UNPKG

react-plotly.js

Version:

A plotly.js react component from Plotly

1 lines • 15.9 kB
{"version":3,"sources":["../src/factory.js","../src/events.js"],"sourcesContent":["import React, {forwardRef, useCallback, useEffect, useRef} from 'react';\n\nimport {eventNames, getPlotlyEventName, getPropName, updateEvents} from './events.js';\n\n// Check if a window is available since SSR (server-side rendering)\n// breaks unnecessarily if you try to use it server-side.\nconst isBrowser = typeof window !== 'undefined';\n\n// Module-level frozen defaults so identity stays stable across renders when a\n// consumer omits the prop. Without this, the `===` checks in the update\n// effect would always see \"changed\" and trigger a needless Plotly.react.\nconst DEFAULT_DATA = Object.freeze([]);\nconst DEFAULT_STYLE = Object.freeze({position: 'relative', display: 'inline-block'});\n\nexport default function plotComponentFactory(Plotly) {\n return forwardRef(function PlotlyComponent(\n {\n data = DEFAULT_DATA,\n layout,\n config,\n frames,\n revision,\n onInitialized,\n onUpdate,\n onPurge,\n onError,\n debug = false,\n style = DEFAULT_STYLE,\n className,\n useResizeHandler = false,\n divId,\n ...eventProps\n },\n forwardedRef\n ) {\n const elRef = useRef(null);\n const promiseRef = useRef(Promise.resolve());\n const handlersRef = useRef({});\n const resizeHandlerRef = useRef(null);\n const unmountingRef = useRef(false);\n const prevRef = useRef(null);\n\n // Refs that mirror the latest values of callbacks consumed by listeners\n // attached during mount (which must keep stable identity across renders).\n const onUpdateRef = useRef(onUpdate);\n onUpdateRef.current = onUpdate;\n const onPurgeRef = useRef(onPurge);\n onPurgeRef.current = onPurge;\n\n // Stable closure for plotly update events. Reads latest `onUpdate` via the\n // ref so the listener identity stays the same across renders — we need the\n // same function reference for both `.on(...)` and `.removeListener(...)`.\n const handleUpdate = useRef(() => {\n const cb = onUpdateRef.current;\n if (typeof cb !== 'function' || !elRef.current) {\n return;\n }\n const el = elRef.current;\n const frames = el._transitionData ? el._transitionData._frames : null;\n cb({data: el.data, layout: el.layout, frames}, el);\n }).current;\n\n const setRef = useCallback(\n (el) => {\n elRef.current = el;\n if (typeof forwardedRef === 'function') {\n forwardedRef(el);\n } else if (forwardedRef) {\n forwardedRef.current = el;\n }\n if (debug && isBrowser && el) {\n window.gd = el;\n }\n },\n [forwardedRef, debug]\n );\n\n // Mount + update effect: no deps array, so it runs after every render —\n // mirroring the original componentDidMount + componentDidUpdate flow.\n useEffect(() => {\n unmountingRef.current = false;\n\n if (prevRef.current === null) {\n prevRef.current = {data, layout, config, frames, revision};\n runPlotlyReact(true, onInitialized, true);\n return;\n }\n\n const prev = prevRef.current;\n const numPrev = prev.frames && prev.frames.length ? prev.frames.length : 0;\n const numNext = frames && frames.length ? frames.length : 0;\n\n const figureChanged = !(\n prev.layout === layout &&\n prev.data === data &&\n prev.config === config &&\n numPrev === numNext\n );\n const revisionWasDefined = prev.revision !== void 0;\n const revisionChanged = prev.revision !== revision;\n\n prevRef.current = {data, layout, config, frames, revision};\n\n if (!figureChanged && (!revisionWasDefined || !revisionChanged)) {\n return;\n }\n\n runPlotlyReact(false, onUpdate, false);\n });\n\n // Cleanup effect — runs on unmount only.\n useEffect(() => {\n const el = elRef.current;\n return () => {\n unmountingRef.current = true;\n if (el) {\n if (typeof onPurgeRef.current === 'function') {\n const frames = el._transitionData ? el._transitionData._frames : null;\n onPurgeRef.current({data: el.data, layout: el.layout, frames}, el);\n }\n if (el.removeListener) {\n updateEvents.forEach((evt) => el.removeListener(evt, handleUpdate));\n }\n Plotly.purge(el);\n }\n if (resizeHandlerRef.current && isBrowser) {\n window.removeEventListener('resize', resizeHandlerRef.current);\n resizeHandlerRef.current = null;\n }\n // Reset refs so StrictMode's re-setup looks like a fresh mount\n prevRef.current = null;\n promiseRef.current = Promise.resolve();\n handlersRef.current = {};\n };\n }, []);\n\n function runPlotlyReact(shouldInvokeResize, figureCallback, shouldAttachUpdateEvents) {\n promiseRef.current = promiseRef.current\n .then(() => {\n if (unmountingRef.current) {\n return void 0;\n }\n if (!elRef.current) {\n throw new Error('Missing element reference');\n }\n return Plotly.react(elRef.current, {data, layout, config, frames});\n })\n .then(() => {\n if (unmountingRef.current) {\n return;\n }\n syncWindowResize(shouldInvokeResize);\n syncEventHandlers();\n invokeFigureCallback(figureCallback);\n if (shouldAttachUpdateEvents) {\n attachUpdateEvents();\n }\n })\n .catch((err) => {\n if (typeof onError === 'function') {\n onError(err);\n }\n });\n }\n\n function invokeFigureCallback(callback) {\n if (typeof callback !== 'function' || !elRef.current) {\n return;\n }\n const el = elRef.current;\n const f = el._transitionData ? el._transitionData._frames : null;\n callback({data: el.data, layout: el.layout, frames: f}, el);\n }\n\n function attachUpdateEvents() {\n if (!elRef.current || !elRef.current.removeListener) {\n return;\n }\n updateEvents.forEach((evt) => elRef.current.on(evt, handleUpdate));\n }\n\n function syncWindowResize(invoke) {\n if (!isBrowser) {\n return;\n }\n if (useResizeHandler && !resizeHandlerRef.current) {\n resizeHandlerRef.current = () => Plotly.Plots.resize(elRef.current);\n window.addEventListener('resize', resizeHandlerRef.current);\n if (invoke) {\n resizeHandlerRef.current();\n }\n } else if (!useResizeHandler && resizeHandlerRef.current) {\n window.removeEventListener('resize', resizeHandlerRef.current);\n resizeHandlerRef.current = null;\n }\n }\n\n function syncEventHandlers() {\n eventNames.forEach((eventName) => {\n const prop = eventProps[getPropName(eventName)];\n const handler = handlersRef.current[eventName];\n const hasHandler = Boolean(handler);\n if (prop && !hasHandler) {\n addEventHandler(eventName, prop);\n } else if (!prop && hasHandler) {\n removeEventHandler(eventName);\n } else if (prop && hasHandler && prop !== handler) {\n removeEventHandler(eventName);\n addEventHandler(eventName, prop);\n }\n });\n }\n\n function addEventHandler(eventName, prop) {\n handlersRef.current[eventName] = prop;\n elRef.current.on(getPlotlyEventName(eventName), prop);\n }\n\n function removeEventHandler(eventName) {\n elRef.current.removeListener(getPlotlyEventName(eventName), handlersRef.current[eventName]);\n delete handlersRef.current[eventName];\n }\n\n return <div id={divId} style={style} ref={setRef} className={className} />;\n });\n}\n","// The single source of truth for the plotly.js events this wrapper forwards.\n//\n// The naming convention is:\n// - events are attached as `'plotly_' + name.toLowerCase()`\n// - react props are `'on' + name`\n//\n// `triggersUpdate` marks events plotly.js emits *after* changing the figure;\n// the wrapper listens to those and fires `onUpdate`. Never set it on a\n// cancelable event — that adds a second listener alongside the consumer's,\n// and plotly keeps only the last listener's return value, so a consumer's\n// `return false` could be discarded. The drill-down clicks rely on\n// `plotly_animated` instead.\n//\n// The `on*` prop types in `factory.d.ts` are maintained by hand against this list\nexport const events = [\n {name: 'AfterExport'},\n {name: 'AfterPlot'},\n {name: 'Animated', triggersUpdate: true},\n {name: 'Animating'},\n {name: 'AnimatingFrame'},\n {name: 'AnimationInterrupted'},\n {name: 'AutoSize'},\n {name: 'BeforeExport'},\n {name: 'BeforeHover'},\n {name: 'BeforePlot'},\n {name: 'ButtonClicked'},\n {name: 'Click'},\n {name: 'ClickAnnotation'},\n {name: 'Deselect'},\n {name: 'DoubleClick', triggersUpdate: true},\n {name: 'Framework'},\n {name: 'Hover'},\n {name: 'IcicleClick'},\n {name: 'LegendClick'},\n {name: 'LegendDoubleClick'},\n {name: 'LegendTitleClick'},\n {name: 'LegendTitleDoubleClick'},\n {name: 'Relayout', triggersUpdate: true},\n {name: 'Relayouting', triggersUpdate: true},\n {name: 'Restyle', triggersUpdate: true},\n {name: 'Redraw', triggersUpdate: true},\n {name: 'Selected'},\n {name: 'Selecting'},\n {name: 'SliderChange'},\n {name: 'SliderEnd'},\n {name: 'SliderStart'},\n {name: 'SunburstClick'},\n {name: 'Transitioned'},\n {name: 'Transitioning'},\n {name: 'TransitionInterrupted'},\n {name: 'TreemapClick'},\n {name: 'Unhover'},\n {name: 'WebGlContextLost'},\n];\n\n/** The plotly.js event name a given entry is attached as. */\nexport function getPlotlyEventName(eventName) {\n return 'plotly_' + eventName.toLowerCase();\n}\n\n/** The React prop name a given entry is read from. */\nexport function getPropName(eventName) {\n return 'on' + eventName;\n}\n\nexport const eventNames = events.map((event) => event.name);\n\nexport const updateEvents = events\n .filter((event) => event.triggersUpdate)\n .map((event) => getPlotlyEventName(event.name));\n"],"mappings":"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","names":["frames","React"]}