From 86e2a389a385f8672caeddeebe024af92f5fc1e7 Mon Sep 17 00:00:00 2001 From: James Ramm Date: Wed, 9 Feb 2022 15:37:16 +0100 Subject: [PATCH 1/5] hook-based API for react-plotly --- src/usePlotly.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/usePlotly.js diff --git a/src/usePlotly.js b/src/usePlotly.js new file mode 100644 index 0000000..42bf937 --- /dev/null +++ b/src/usePlotly.js @@ -0,0 +1,50 @@ +import { useLayoutEffect, useState, useMemo } from 'react'; +import { head, prop, compose, pick, objOf, mergeDeepRight } from 'ramda'; +import { stream, scan } from 'flyd'; + +/** +* A simple debouncing function +*/ +const debounce = (fn, delay) => { + let timeout; + + return function (...args) { + const functionCall = () => fn.apply(this, args); + + timeout && clearTimeout(timeout); + timeout = setTimeout(functionCall, delay); + }; +}; + +const getSizeForLayout = compose(objOf('layout'), pick(['width', 'height']), prop('contentRect'), head); + +export default function usePlotly(type) { + const updates = useMemo(stream, []); + const appendData = useMemo(stream, []); + const plotlyState = useMemo( + () => scan(mergeDeepRight, { data: [], config: {}, layout: {} }, updates), + [] + ); + + const observer = new ResizeObserver(debounce(compose(updates, getSizeForLayout), 100)); + const [internalRef, setRef] = useState(null); + useLayoutEffect(() => { + if (internalRef) { + observer.observe(internalRef); + const endS = plotlyState.map(state => { + Plotly.react(internalRef, state); + }); + + const endAppend = appendData.map(({ data, tracePos }) => Plotly.extendTraces(internalRef, data, tracePos)); + + return () => { + Plotly.purge(internalRef); + observer.unobserve(internalRef); + endAppend.end(true); + endS.end(true); + }; + } + }, [internalRef, plotlyState, updates, appendData]); + + return { ref: setRef, updates, appendData }; +} From aa200c9fd767eaed6ffe1dcd2f5cf017977483ee Mon Sep 17 00:00:00 2001 From: James Ramm Date: Wed, 9 Feb 2022 15:39:40 +0100 Subject: [PATCH 2/5] Update usePlotly.js Remove unused argument --- src/usePlotly.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usePlotly.js b/src/usePlotly.js index 42bf937..b087988 100644 --- a/src/usePlotly.js +++ b/src/usePlotly.js @@ -18,7 +18,7 @@ const debounce = (fn, delay) => { const getSizeForLayout = compose(objOf('layout'), pick(['width', 'height']), prop('contentRect'), head); -export default function usePlotly(type) { +export default function usePlotly() { const updates = useMemo(stream, []); const appendData = useMemo(stream, []); const plotlyState = useMemo( From 1d0b7fcaa74836e907502d29805c3a32019fcbd5 Mon Sep 17 00:00:00 2001 From: James Ramm Date: Wed, 9 Feb 2022 15:42:02 +0100 Subject: [PATCH 3/5] Update package.json Add dependencies for usePlotly hook --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 945785a..8775809 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,9 @@ }, "peerDependencies": { "plotly.js": ">1.34.0", - "react": ">0.13.0" + "react": ">0.13.0", + "flyd": ">0.2.8", + "ramda": ">0.28.0" }, "browserify-global-shim": { "react": "React" From 95cb40d703d8ee976dca4e09657d06a65cfe2bbd Mon Sep 17 00:00:00 2001 From: James Ramm Date: Thu, 10 Mar 2022 09:03:04 +0100 Subject: [PATCH 4/5] Fix version strings --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8775809..5d0f853 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,8 @@ "peerDependencies": { "plotly.js": ">1.34.0", "react": ">0.13.0", - "flyd": ">0.2.8", - "ramda": ">0.28.0" + "flyd": ">=0.2.8", + "ramda": ">=0.28.0" }, "browserify-global-shim": { "react": "React" From eb226fe35158d7515b88936234573cf361b920ab Mon Sep 17 00:00:00 2001 From: James Ramm Date: Thu, 2 Jun 2022 12:02:13 +0200 Subject: [PATCH 5/5] Update README Adds example of `usePlotly` --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index e45df6e..b6c6613 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,34 @@ In short, this means that simply adding data points to a trace in `data` or chan ## API Reference +### usePlotly Hook + +As an alternative to the `Plot` component, you may use the `usePlotly` react _hook_. This provides a more powerful API with full control over the plot element, compatibility with functional components, intuitive responsive behaviour and ability to use `extendTraces`. +Here is a simple example of creating a chart with `usePlotly`: + +```jsx +function MyChart(props) { + const { ref, updates, appendData } = usePlotly(); + + // Here is a function that will change the data. You must pass a partial Figure object (plotly DSL object) which will be + // merged with all previous calls to `updates` + const changeData = () => updates({ data: [ { y: [Math.random() * 10], type: 'scatter' } ] }) + + // Here we start extending traces using the `appendData` stream + const extendData = setInterval(() => { + appendData({ data: { y: [[Math.random() * 10]]}, tracePos: [0] }); + }, 500); + + return ( +
+
+ + +
); +} +``` + + ### Basic Props **Warning**: for the time being, this component may _mutate_ its `layout` and `data` props in response to user input, going against React rules. This behaviour will change in the near future once https://github.com/plotly/plotly.js/issues/2389 is completed.