From 1064832af0ca6c46c64353ec8f72d954745cb99d Mon Sep 17 00:00:00 2001
From: Manfred Cheung
Date: Wed, 11 Jun 2025 18:33:19 -0400
Subject: [PATCH 1/5] add playUpdates function to client-api
---
projects/client-api/src/index.js | 45 +++++++++++++++++++++++++++++++-
projects/client-api/src/rxjs.js | 2 ++
2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/projects/client-api/src/index.js b/projects/client-api/src/index.js
index 1f41114..030aec1 100644
--- a/projects/client-api/src/index.js
+++ b/projects/client-api/src/index.js
@@ -133,6 +133,7 @@ import {
mergeAll,
Observable,
of,
+ pairwise,
pipe,
ReplaySubject,
BehaviorSubject,
@@ -1670,6 +1671,48 @@ export function subscribeLabels({ onChange, onExit, onError, g }) {
.subscribe({ error: onError });
}
+/**
+ * Subscribe to graph simulation completion event
+ * @function subscribeLabels
+ * @param {@link GraphistryState} [g] A {@link GraphistryState} {@link Observable} or depricated, cache an object.
+ * @return {Subscription} A {@link Subscription} that can be used to react to the play updates
+ * @example
+ * GraphistryJS(document.getElementById('viz'))
+ * .pipe(
+ * map(playUpdates),
+ * tap(() => console.log('Play completed')),
+ * })
+ * .subscribe();
+ */
+export function playUpdates(g) {
+ if (!(g.subscriptionAPIVersion >= 1)) {
+ return throwError(() => new Error('playUpdates is not available the currently embedded graphistry viz.'));
+ }
+
+ const selectionPath = ".labels";
+ return (new BehaviorSubject('Initialize playUpdates stream')
+ .pipe(
+ tap(() => {
+ console.debug('postMessage subscription', '@client-api.playUpdate');
+ g.iFrame.contentWindow.postMessage({ type: 'graphistry-subscribe', agent: 'graphistryjs', path: selectionPath }, '*');
+ }),
+ finalize(() => {
+ console.debug('postMessage unsubscribe', '@client-api.playUpdate');
+ g.iFrame.contentWindow.postMessage({ type: 'graphistry-unsubscribe', agent: 'graphistryjs', path: selectionPath }, '*');
+ }),
+ switchMap(() =>
+ fromEvent(window, 'message').pipe(
+ map(o => o.data),
+ filter(o => o && o.type === 'graphistry-sub-update' && o.path === selectionPath),
+ map(o => o.data),
+ pairwise(),
+ filter(([{ simulating: prevSim }, { simulating: currSim }]) => prevSim && !currSim),
+ shareReplay({ bufferSize: 1, refCount: true })
+ ),
+ )
+ ));
+}
+
class GraphistryState {
constructor(subscriptionAPIVersion, iFrame, models, result) {
@@ -1768,7 +1811,7 @@ function addCallbacks(obs, target) {
target.labelUpdates = labelUpdates;// lift(obs, labelUpdates);
target.subscribeLabels = subscribeLabels;//lift(obs, subscribeLabels);
target.selectionUpdates = selectionUpdates; // lift(obs, selectionUpdates);
- target.subscribeLabels
+ target.playUpdates = playUpdates;
return target;
}
diff --git a/projects/client-api/src/rxjs.js b/projects/client-api/src/rxjs.js
index 952444f..7a86efc 100644
--- a/projects/client-api/src/rxjs.js
+++ b/projects/client-api/src/rxjs.js
@@ -28,6 +28,7 @@ import {
map,
mergeMap,
mergeAll,
+ pairwise,
scan,
share,
shareReplay,
@@ -58,6 +59,7 @@ export {
mergeAll,
Observable,
of,
+ pairwise,
pipe,
ReplaySubject,
retryWhen,
From 4600e89cef32ec14afc5f2f3409bcfb76a00dc89 Mon Sep 17 00:00:00 2001
From: Manfred Cheung
Date: Wed, 11 Jun 2025 18:36:20 -0400
Subject: [PATCH 2/5] add client-api-react onPlayComplete handler
---
projects/client-api-react/src/index.js | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/projects/client-api-react/src/index.js b/projects/client-api-react/src/index.js
index 9d430cd..bb6075b 100644
--- a/projects/client-api-react/src/index.js
+++ b/projects/client-api-react/src/index.js
@@ -9,7 +9,7 @@ import * as gAPI from '@graphistry/client-api';
import { ajax, catchError, first, forkJoin, map, of, switchMap, tap } from '@graphistry/client-api'; // avoid explicit rxjs dep
import { bg } from './bg';
import { bindings, panelNames, calls } from './bindings.js';
-import { Client as ClientBase, ClientPKey as ClientPKeyBase, selectionUpdates, subscribeLabels } from '@graphistry/client-api';
+import { Client as ClientBase, ClientPKey as ClientPKeyBase, selectionUpdates, subscribeLabels, playUpdates } from '@graphistry/client-api';
export const Client = ClientBase;
export const ClientPKey = ClientPKeyBase;
@@ -104,6 +104,7 @@ const propTypes = {
onUpdateObservableG: PropTypes.func,
onSelectionUpdate: PropTypes.func,
onLabelsUpdate: PropTypes.func,
+ onPlayComplete: PropTypes.func,
selectionUpdateOptions: PropTypes.object,
queryParamExtra: PropTypes.object
@@ -467,6 +468,7 @@ const Graphistry = forwardRef((props, ref) => {
onUpdateObservableG,
onSelectionUpdate,
onLabelsUpdate,
+ onPlayComplete,
selectionUpdateOptions
} = props;
@@ -558,6 +560,20 @@ const Graphistry = forwardRef((props, ref) => {
}
}, [g, onLabelsUpdate])
+ useEffect(() => {
+ if (g && onPlayComplete) {
+ const sub = playUpdates(g)
+ .subscribe(
+ (v) => onPlayComplete(undefined, v),
+ (error) => onPlayComplete(error)
+ );
+
+ return () => {
+ sub && sub.unsubscribe();
+ };
+ }
+ }, [g, onPlayComplete])
+
const playNormalized = typeof play === 'boolean' ? play : (play | 0) * 1000;
const optionalParams = (type ? `&type=${type}` : ``) +
(controls ? `&controls=${controls}` : ``) +
From 8082d73f8002ab15744f8f5045d6922136d4e4c3 Mon Sep 17 00:00:00 2001
From: Manfred Cheung
Date: Wed, 11 Jun 2025 18:54:54 -0400
Subject: [PATCH 3/5] add onPlayComplete story
---
.../src/stories/Graphistry.stories.jsx | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/projects/client-api-react/src/stories/Graphistry.stories.jsx b/projects/client-api-react/src/stories/Graphistry.stories.jsx
index 70d4162..9e6d5f3 100644
--- a/projects/client-api-react/src/stories/Graphistry.stories.jsx
+++ b/projects/client-api-react/src/stories/Graphistry.stories.jsx
@@ -97,6 +97,24 @@ export const OnLabelUpdate = {
},
};
+export const OnPlayUpdate = {
+ render: (args) => {
+ const [numPlayComplete, setNumPlayComplete] = useState(0);
+
+ const onLabelsUpdate = () => {
+ console.log('onLabelsUpdate');
+ setNumPlayComplete(numPlayComplete++);
+ };
+
+ return (
+
+ {`Number of play completed: ${numPlayComplete}`}
+
+
+ );
+ },
+};
+
export const NoClusteringOnLoad = {
render: (args) => ,
};
From 3bc56e78bbd7ede3f2250ba3e246c057149c728d Mon Sep 17 00:00:00 2001
From: Manfred Cheung
Date: Wed, 11 Jun 2025 19:07:16 -0400
Subject: [PATCH 4/5] add changelog
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19c4da8..19ecb8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
### Added
* **client-api-react**: Add `pointStrokeWidth` and `showCollections` bindings
+* **client-api**: Add `playUpdates` observer which creates event when graph simulation completes
+* **client-api-react**: Add `onPlayComplete` callback property
### Security
From 0a5f108053f3dc43fc74238a443a2b957ed5ad5d Mon Sep 17 00:00:00 2001
From: Manfred Cheung
Date: Mon, 16 Jun 2025 13:38:58 -0400
Subject: [PATCH 5/5] PR feedback fixes
---
.../client-api-react/src/stories/Graphistry.stories.jsx | 6 +++---
projects/client-api/src/index.js | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/projects/client-api-react/src/stories/Graphistry.stories.jsx b/projects/client-api-react/src/stories/Graphistry.stories.jsx
index 9e6d5f3..77e6dc9 100644
--- a/projects/client-api-react/src/stories/Graphistry.stories.jsx
+++ b/projects/client-api-react/src/stories/Graphistry.stories.jsx
@@ -101,15 +101,15 @@ export const OnPlayUpdate = {
render: (args) => {
const [numPlayComplete, setNumPlayComplete] = useState(0);
- const onLabelsUpdate = () => {
+ const onPlayUpdate = () => {
console.log('onLabelsUpdate');
- setNumPlayComplete(numPlayComplete++);
+ setNumPlayComplete(numPlayComplete + 1);
};
return (
{`Number of play completed: ${numPlayComplete}`}
-
+
);
},
diff --git a/projects/client-api/src/index.js b/projects/client-api/src/index.js
index 030aec1..aa988c4 100644
--- a/projects/client-api/src/index.js
+++ b/projects/client-api/src/index.js
@@ -1511,7 +1511,7 @@ export function subscribeSelections({ onChange, g }) {
* The inner {@link Observable} for a label will complete if the label is removed from the screen.
*
* @function labelUpdates
- * @param {@link GraphistryState} [g] A {@link GraphistryState} {@link Observable} or depricated, cache an object.
+ * @param {@link GraphistryState} [g] A {@link GraphistryState} {@link Observable} or deprecated, cache an object.
* @return {Observable>} An {@link Observable} of inner {Observables}, where each
* inner {@link Observable} represents the lifetime of a label in the visualization.
* @example
@@ -1673,8 +1673,8 @@ export function subscribeLabels({ onChange, onExit, onError, g }) {
/**
* Subscribe to graph simulation completion event
- * @function subscribeLabels
- * @param {@link GraphistryState} [g] A {@link GraphistryState} {@link Observable} or depricated, cache an object.
+ * @function playUpdates
+ * @param {@link GraphistryState} [g] A {@link GraphistryState} {@link Observable}
* @return {Subscription} A {@link Subscription} that can be used to react to the play updates
* @example
* GraphistryJS(document.getElementById('viz'))