From 97fe788dff24ebcfd4e73ac7974d29e7f891c442 Mon Sep 17 00:00:00 2001 From: Michele Ceriotti Date: Fri, 10 Nov 2023 02:26:35 -0800 Subject: [PATCH 1/2] Make selected structure a traitlet accessible from python Currently broken... --- python/chemiscope/jupyter.py | 5 +++-- python/jupyter/src/widget.ts | 40 ++++++++++++++++++++++++++++++++++++ src/index.ts | 21 ++++++++++++++++++- src/info/info.ts | 4 ++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/python/chemiscope/jupyter.py b/python/chemiscope/jupyter.py index 828fda4f0..5febdd256 100644 --- a/python/chemiscope/jupyter.py +++ b/python/chemiscope/jupyter.py @@ -25,8 +25,9 @@ class ChemiscopeWidgetBase(ipywidgets.DOMWidget, ipywidgets.ValueWidget): # change what's being displayed by chemiscope, but you need to assign a full # dictionary (`widget.settings["map"]["x"]["property"] = "foo"` will not # work, but `widget.settings = updated_settings` will). - settings = Dict().tag(sync=True) - # switch to disable automatic update of settings + settings = Dict().tag(sync=True) + selection = Dict().tag(sync=True) + # switch to disable automatic update of settings (& se;lection) _settings_sync = Bool().tag(sync=True) def __init__(self, data, has_metadata): diff --git a/python/jupyter/src/widget.ts b/python/jupyter/src/widget.ts index b0ca3ca85..eb36e1a4c 100644 --- a/python/jupyter/src/widget.ts +++ b/python/jupyter/src/widget.ts @@ -8,6 +8,7 @@ import './widget.css'; import { DefaultVisualizer, MapVisualizer, StructureVisualizer } from '../../../src/index'; import { Dataset, Settings } from '../../../src/dataset'; +import { Indexes } from '../../../src/indexer'; const PlausibleTracker = Plausible({ domain: 'jupyter.chemiscope.org', @@ -71,6 +72,41 @@ class ChemiscopeBaseView extends DOMWidgetView { this.model.save_changes(); } } + + protected _bindPythonSelection(): void { + // update settings on the JS side when they are changed in Python + this.model.on( + 'change:selection', + () => { + // only trigger a visualizer update if required. + // this is also used to avoid an infinite loop when settings are changed JS-side + if (!this.model.get('_settings_sync')) { + return; + } + + const selection = this.model.get('selection') as Indexes; + console.log("selection update", selection); + // for the moment does nothing + }, + this + ); + } + + protected _updatePythonSelection(): void { + if (this.visualizer !== undefined) { + const selection = this.visualizer.getSelected(); + + // save current settings of settings_sync + const sync_state = this.model.get('_settings_sync') as unknown; + + this.model.set('_settings_sync', false); + this.model.save_changes(); + this.model.set('selection', selection); + this.model.save_changes(); + this.model.set('_settings_sync', sync_state); + this.model.save_changes(); + } + } } /** @@ -135,6 +171,7 @@ export class ChemiscopeView extends ChemiscopeBaseView { }; this._bindPythonSettings(); + this._bindPythonSelection(); const data = JSON.parse(this.model.get('value') as string) as Dataset; void DefaultVisualizer.load(config, data) @@ -142,6 +179,7 @@ export class ChemiscopeView extends ChemiscopeBaseView { this.visualizer = visualizer; // update the Python side settings whenever a setting changes this.visualizer.onSettingChange(() => this._updatePythonSettings()); + this.visualizer.onSelection = (() => this._updatePythonSelection()); // and set them to the initial value right now this._updatePythonSettings(); }) @@ -216,6 +254,7 @@ export class StructureView extends ChemiscopeBaseView { }; this._bindPythonSettings(); + this._bindPythonSelection(); const data = JSON.parse(this.model.get('value') as string) as Dataset; void StructureVisualizer.load(config, data) @@ -298,6 +337,7 @@ export class MapView extends ChemiscopeBaseView { }; this._bindPythonSettings(); + this._bindPythonSelection(); const data = JSON.parse(this.model.get('value') as string) as Dataset; void MapVisualizer.load(config, data) diff --git a/src/index.ts b/src/index.ts index 1fd275801..e71c54c9e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -379,7 +379,7 @@ class DefaultVisualizer { this.structure.applySettings(settings.structure as Settings[]); } } - + /** * Add the given `callback` to be called whenever a setting changes. The * callback will be given the path to the settings as a list of keys; and @@ -403,6 +403,16 @@ class DefaultVisualizer { }); } + public getSelected(): Indexes { + return this.info.getSelected(); + } + + /*public onSelectedChange(callback: (keys: string[], value: unknown) => void): void { + this.info.onSelectedChange( (keys, value) ==> { + callback(keys, value); + }); + }*/ + /** * Get the dataset used to create the current visualization * @@ -525,6 +535,7 @@ class StructureVisualizer { }; let initial: Indexes = { environment: 0, structure: 0, atom: 0 }; + // if we have sparse environments, make sure to use the first // environment actually part of the dataset if (dataset.environments !== undefined) { @@ -595,6 +606,10 @@ class StructureVisualizer { callback(keys, value); }); } + + public getSelected(): Indexes { + return this.info.getSelected(); + } } /** @@ -752,6 +767,10 @@ class MapVisualizer { callback(keys, value); }); } + + public getSelected(): Indexes { + return this.info.getSelected(); + } } declare const CHEMISCOPE_GIT_VERSION: string; diff --git a/src/info/info.ts b/src/info/info.ts index 95b5b3899..dc285c569 100644 --- a/src/info/info.ts +++ b/src/info/info.ts @@ -395,4 +395,8 @@ export class EnvironmentInfo { assert(indexes !== undefined); return indexes; } + + public getSelected(): Indexes { + return this._indexes() + } } From ae0ef527a60eda9c4d7768bbb0033d9e6923da14 Mon Sep 17 00:00:00 2001 From: Michele Ceriotti Date: Sun, 12 Nov 2023 17:37:10 -0800 Subject: [PATCH 2/2] Python-side structure selection --- python/jupyter/src/widget.ts | 11 +++++++---- src/index.ts | 17 ++++++++++++++--- src/info/info.ts | 12 ++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/python/jupyter/src/widget.ts b/python/jupyter/src/widget.ts index eb36e1a4c..92f79fb28 100644 --- a/python/jupyter/src/widget.ts +++ b/python/jupyter/src/widget.ts @@ -86,6 +86,8 @@ class ChemiscopeBaseView extends DOMWidgetView { const selection = this.model.get('selection') as Indexes; console.log("selection update", selection); + + (this.visualizer as DefaultVisualizer).select(selection); // for the moment does nothing }, this @@ -95,7 +97,7 @@ class ChemiscopeBaseView extends DOMWidgetView { protected _updatePythonSelection(): void { if (this.visualizer !== undefined) { const selection = this.visualizer.getSelected(); - + console.log("getting selection ", selection); // save current settings of settings_sync const sync_state = this.model.get('_settings_sync') as unknown; @@ -175,13 +177,14 @@ export class ChemiscopeView extends ChemiscopeBaseView { const data = JSON.parse(this.model.get('value') as string) as Dataset; void DefaultVisualizer.load(config, data) - .then((visualizer) => { + .then((visualizer) => { this.visualizer = visualizer; // update the Python side settings whenever a setting changes - this.visualizer.onSettingChange(() => this._updatePythonSettings()); - this.visualizer.onSelection = (() => this._updatePythonSelection()); + this.visualizer.onSettingChange(() => this._updatePythonSettings()); + this.visualizer.info.onselection = (() => this._updatePythonSelection()); // and set them to the initial value right now this._updatePythonSettings(); + this._updatePythonSelection(); }) .catch((e: Error) => { // eslint-disable-next-line no-console diff --git a/src/index.ts b/src/index.ts index e71c54c9e..3645b0163 100644 --- a/src/index.ts +++ b/src/index.ts @@ -171,7 +171,7 @@ class DefaultVisualizer { public map: PropertiesMap; public info: EnvironmentInfo; public meta: MetadataPanel; - public structure: ViewersGrid; + public structure: ViewersGrid; private _indexer: EnvironmentIndexer; // Stores raw input input so we can give it back later @@ -214,7 +214,7 @@ class DefaultVisualizer { this.structure.onselect = (indexes) => { this.map.select(indexes); - this.info.show(indexes); + this.info.show(indexes); }; this.structure.onremove = (guid) => { @@ -240,8 +240,9 @@ class DefaultVisualizer { ); this.map.onselect = (indexes) => { + console.log("map onselect"); this.info.show(indexes); - this.structure.show(indexes); + this.structure.show(indexes); }; this.map.activeChanged = (guid, indexes) => { @@ -256,9 +257,14 @@ class DefaultVisualizer { this._indexer, dataset.parameters ); + this.info.onchange = (indexes) => { this.map.select(indexes); this.structure.show(indexes); + if (this.info.onselection !== undefined) { + console.log("info onchange", indexes); + this.info.onselection(); + } }; this.structure.delayChanged = (delay) => { @@ -407,6 +413,11 @@ class DefaultVisualizer { return this.info.getSelected(); } + public select(indexes: Indexes) { + console.log("selecting"); + this.map.select(indexes); + this.info.show(indexes); + } /*public onSelectedChange(callback: (keys: string[], value: unknown) => void): void { this.info.onSelectedChange( (keys, value) ==> { callback(keys, value); diff --git a/src/info/info.ts b/src/info/info.ts index dc285c569..a6be0595d 100644 --- a/src/info/info.ts +++ b/src/info/info.ts @@ -51,6 +51,10 @@ interface Info { export class EnvironmentInfo { /** Callback used when the user changes one of the sliders value */ public onchange: (indexes: Indexes) => void; + + /** Callback used when the selection is changed, no matter the source */ + public onselection?: () => void;; + /** delay in ms between "frames" during playback over structures/atoms */ public playbackDelay: number; @@ -138,6 +142,7 @@ export class EnvironmentInfo { /** Show properties for the given `indexes`, and update the sliders values */ public show(indexes: Indexes): void { + console.log("info/show"); const previousStructure = this._indexes().structure; this._structure.number.value = `${indexes.structure + 1}`; @@ -163,6 +168,11 @@ export class EnvironmentInfo { this._atom.table.show(indexes); } } + + if (this.onselection !== undefined) { + console.log("info show trigger onselection"); + this.onselection(); + } } /** @@ -220,6 +230,7 @@ export class EnvironmentInfo { }; slider.onchange = () => { + console.log("slider/onchange"); const structure = this._structure.slider.value(); if (this._atom !== undefined) { @@ -256,6 +267,7 @@ export class EnvironmentInfo { number.max = this._indexer.structuresCount().toString(); number.onchange = () => { + console.log("number/onchange"); const value = parseInt(number.value, 10) - 1; if (isNaN(value) || value < 0 || value >= parseInt(number.max, 10)) { // reset to the current slider value if we got an invalid value