Skip to content

Commit

Permalink
Add InteractiveExplorer, InteractiveEditor. Closes #1768 (#1770)
Browse files Browse the repository at this point in the history
* WIP Add initial commit of DatasetExplorer

* WIP use artifact name in DataExplorer

* WIP Add plot editor to dataset explorer

* WIP add PlotEditor and css

* Add UI elements for adding/removing data

* Fix UI issue with editing plotted data

* WIP Add utility for parsing python slices

* Add slice string validation

* UI support for validating data slice

* Update figure updates given the figure data

* Add explorer_helpers.py for DatasetExplorer

* Add explorer helpers to session for getting metadata

* dynamically create variable names for dropdown

* Add tests for variable name creation from metadata

* Plot actual data selected from "Add Data" button

* Fix plot height

* Add basic 3D plot support to DataExplorer

* Fix async issue with multiple lines

* Fix x-axis, y-axis labels

* Add validation for plotting data

* Remove .only from PythonSlice test suite

* Add basic artifact loader to DatasetExplorer

* Only show artifacts with data in artifact loader

* Update metadata on artifact load into session

* Removed hardcoded examples from html

* Add some support for colors (uniform only)

* Add color support for individual points

* Fix selection of keys from artifacts

* WIP Use session with queue in dataset explorer

* WIP code cleanup dataset explorer

* Add compute creation (and shield) for DatasetExplorer

* Don't show slice syntax errors until change event

* Fix artifacts with extensions. minor code cleanup

* Increase territory for access to initialization code

(custom serializer support)

* Rename DatasetVisualizer -> TensorPlotter

* Rename scss,css files

* Add "save" action to floating action button

* Only load jscolor in the browser

* Skip jscolor library when linting

* Add InteractiveEditor base class

* Update to use InteractiveEditor base classes

* Add getSnapshot to tensor plotter

* Fix setting the data dialog on open

* WIP working on operation code...

* Include all artifacts in TensorPlotter

* Fixed python slice parsing

* Add InteractiveExplorer base class

* Use inform dialog w/ auth errors

* Update TensorPlotter to inherit from InteractiveExplorer

* fix css linting issue

* Remove TensorPlotter

* Remove tensor plotter from registries

* Remove more tensorplotter things

* Remove TensorPlotter tests

* Remove old comments and minor fixes

* remove old comment
  • Loading branch information
brollb authored Jul 15, 2020
1 parent d383d00 commit 5a62fea
Show file tree
Hide file tree
Showing 11 changed files with 595 additions and 0 deletions.
218 changes: 218 additions & 0 deletions src/visualizers/panels/InteractiveEditor/InteractiveEditorControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*globals define, WebGMEGlobal*/

define([
'deepforge/viz/ConfigDialog',
'js/Constants',
], function (
ConfigDialog,
CONSTANTS,
) {

'use strict';

class InteractiveEditorControl {
constructor(options) {
this._logger = options.logger.fork('Control');
this.client = options.client;
this._embedded = options.embedded;
this._widget = options.widget;
this.initializeWidgetHandlers(this._widget);
this.territoryEventFilters = [];

this._currentNodeId = null;

this._logger.debug('ctor finished');
}

initializeWidgetHandlers (widget) {
const features = widget.getCapabilities();
if (features.save) {
widget.save = () => this.save();
}
widget.getConfigDialog = () => new ConfigDialog(this.client);
}

selectedObjectChanged (nodeId) {
const desc = this.getObjectDescriptor(nodeId);

this._logger.debug('activeObject nodeId \'' + nodeId + '\'');

if (this._currentNodeId) {
this.client.removeUI(this._territoryId);
}

this._currentNodeId = nodeId;

if (typeof this._currentNodeId === 'string') {
const territory = this.getTerritory(nodeId);
this._widget.setTitle(desc.name.toUpperCase());

this._territoryId = this.client
.addUI(this, events => this._eventCallback(events));

this.client.updateTerritory(this._territoryId, territory);
}
}

getTerritory(nodeId) {
const territory = {};
territory[nodeId] = {children: 0};
return territory;
}

getMetaNode(name) {
const metanodes = this.client.getAllMetaNodes();
return metanodes
.find(node => {
const namespace = node.getNamespace();
const fullName = namespace ? namespace + '.' + node.getAttribute('name') :
node.getAttribute('name');

return fullName === name;
});
}

createNode(desc, parentId) {
if (!parentId) {
parentId = this._currentNodeId;
}
desc.pointers = desc.pointers || {};
desc.attributes = desc.attributes || {};

const base = this.getMetaNode(desc.type) || this.client.getNode(desc.pointers.base);
const nodeId = this.client.createNode({
parentId: parentId,
baseId: base.getId()
});

const attributes = Object.entries(desc.attributes);
attributes.forEach(entry => {
const [name, value] = entry;
this.client.setAttribute(nodeId, name, value);
});

const pointers = Object.entries(desc.pointers);
pointers.forEach(entry => {
const [name, id] = entry;
this.client.setPointer(nodeId, name, id);
});

return nodeId;
}

save() {
this.client.startTransaction();
const dataId = this.createNode(this._widget.getSnapshot());
const implicitOpId = this.createNode(this._widget.getEditorState(), dataId);
this.client.setPointer(dataId, 'provenance', implicitOpId);
const operationId = this.createNode(this._widget.getOperation(), implicitOpId);
this.client.setPointer(implicitOpId, 'operation', operationId);
this.client.completeTransaction();
}

getObjectDescriptor (nodeId) {
const node = this.client.getNode(nodeId);

if (node) {
return {
id: node.getId(),
name: node.getAttribute('name'),
childrenIds: node.getChildrenIds(),
parentId: node.getParentId(),
};
}
}

/* * * * * * * * Node Event Handling * * * * * * * */
_eventCallback (events=[]) {
this._logger.debug('_eventCallback \'' + events.length + '\' items');

events
.filter(event => this.isRelevantEvent(event))
.forEach(event => {
switch (event.etype) {

case CONSTANTS.TERRITORY_EVENT_LOAD:
this.onNodeLoad(event.eid);
break;
case CONSTANTS.TERRITORY_EVENT_UPDATE:
this.onNodeUpdate(event.eid);
break;
case CONSTANTS.TERRITORY_EVENT_UNLOAD:
this.onNodeUnload(event.eid);
break;
default:
break;
}
});

this._logger.debug('_eventCallback \'' + events.length + '\' items - DONE');
}

onNodeLoad (gmeId) {
const description = this.getObjectDescriptor(gmeId);
this._widget.addNode(description);
}

onNodeUpdate (gmeId) {
const description = this.getObjectDescriptor(gmeId);
this._widget.updateNode(description);
}

onNodeUnload (gmeId) {
this._widget.removeNode(gmeId);
}

isRelevantEvent (event) {
return this.territoryEventFilters
.reduce((keep, fn) => keep && fn(event), true);
}

_stateActiveObjectChanged (model, activeObjectId) {
if (this._currentNodeId === activeObjectId) {
// The same node selected as before - do not trigger
} else {
this.selectedObjectChanged(activeObjectId);
}
}

/* * * * * * * * Visualizer life cycle callbacks * * * * * * * */
destroy () {
this._detachClientEventListeners();
}

_attachClientEventListeners () {
this._detachClientEventListeners();
if (!this._embedded) {
WebGMEGlobal.State.on(
'change:' + CONSTANTS.STATE_ACTIVE_OBJECT,
this._stateActiveObjectChanged,
this
);
}
}

_detachClientEventListeners () {
if (!this._embedded) {
WebGMEGlobal.State.off(
'change:' + CONSTANTS.STATE_ACTIVE_OBJECT,
this._stateActiveObjectChanged
);
}
}

onActivate () {
this._attachClientEventListeners();

if (typeof this._currentNodeId === 'string') {
WebGMEGlobal.State.registerActiveObject(this._currentNodeId, {suppressVisualizerFromNode: true});
}
}

onDeactivate () {
this._detachClientEventListeners();
}
}

return InteractiveEditorControl;
});
97 changes: 97 additions & 0 deletions src/visualizers/panels/InteractiveEditor/InteractiveEditorPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*globals define, _, WebGMEGlobal*/

define([
'js/PanelBase/PanelBaseWithHeader',
'js/PanelManager/IActivePanel',
'widgets/InteractiveEditor/InteractiveEditorWidget',
'./InteractiveEditorControl'
], function (
PanelBaseWithHeader,
IActivePanel,
InteractiveEditorWidget,
InteractiveEditorControl
) {
'use strict';

function InteractiveEditorPanel(layoutManager, params) {
var options = {};
//set properties from options
options[PanelBaseWithHeader.OPTIONS.LOGGER_INSTANCE_NAME] = 'InteractiveEditorPanel';
options[PanelBaseWithHeader.OPTIONS.FLOATING_TITLE] = true;

//call parent's constructor
PanelBaseWithHeader.apply(this, [options, layoutManager]);

this._client = params.client;
this._embedded = params.embedded;

this.initialize();

this.logger.debug('ctor finished');
}

//inherit from PanelBaseWithHeader
_.extend(InteractiveEditorPanel.prototype, PanelBaseWithHeader.prototype);
_.extend(InteractiveEditorPanel.prototype, IActivePanel.prototype);

InteractiveEditorPanel.prototype.initialize = function () {
var self = this;

//set Widget title
this.setTitle('');

this.widget = new InteractiveEditorWidget(this.logger, this.$el);

this.widget.setTitle = function (title) {
self.setTitle(title);
};

this.control = new InteractiveEditorControl({
logger: this.logger,
client: this._client,
embedded: this._embedded,
widget: this.widget
});

this.onActivate();
};

/* OVERRIDE FROM WIDGET-WITH-HEADER */
/* METHOD CALLED WHEN THE WIDGET'S READ-ONLY PROPERTY CHANGES */
InteractiveEditorPanel.prototype.onReadOnlyChanged = function (isReadOnly) {
//apply parent's onReadOnlyChanged
PanelBaseWithHeader.prototype.onReadOnlyChanged.call(this, isReadOnly);

};

InteractiveEditorPanel.prototype.onResize = function (width, height) {
this.logger.debug('onResize --> width: ' + width + ', height: ' + height);
this.widget.onWidgetContainerResize(width, height);
};

/* * * * * * * * Visualizer life cycle callbacks * * * * * * * */
InteractiveEditorPanel.prototype.destroy = function () {
this.control.destroy();
this.widget.destroy();

PanelBaseWithHeader.prototype.destroy.call(this);
WebGMEGlobal.KeyboardManager.setListener(undefined);
WebGMEGlobal.Toolbar.refresh();
};

InteractiveEditorPanel.prototype.onActivate = function () {
this.widget.onActivate();
this.control.onActivate();
WebGMEGlobal.KeyboardManager.setListener(this.widget);
WebGMEGlobal.Toolbar.refresh();
};

InteractiveEditorPanel.prototype.onDeactivate = function () {
this.widget.onDeactivate();
this.control.onDeactivate();
WebGMEGlobal.KeyboardManager.setListener(undefined);
WebGMEGlobal.Toolbar.refresh();
};

return InteractiveEditorPanel;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*globals define */

define([
'panels/InteractiveEditor/InteractiveEditorControl',
], function (
InteractiveEditorControl,
) {

'use strict';

class InteractiveExplorerControl extends InteractiveEditorControl {
ensureValidSnapshot(desc) {
const metadata = this.getMetaNode('pipeline.Metadata');
const type = this.getMetaNode(desc.type);

if (!type) {
throw new Error(`Invalid metadata type: ${type}`);
}

if (!type.isTypeOf(metadata.getId())) {
throw new Error('Explorer can only create artifact metadata');
}
}

save() {
const snapshotDesc = this._widget.getSnapshot();
this.ensureValidSnapshot(snapshotDesc);

const features = this._widget.getCapabilities();
this.client.startTransaction();
const data = this.createNode(snapshotDesc);
if (features.provenance) {
const implicitOp = this.createNode(this._widget.getEditorState(), data);
this.client.setPointer(data.getId(), 'provenance', implicitOp.getId());
const operation = this.createNode(this._widget.getOperation(), implicitOp);
this.client.setPointer(implicitOp.getId(), 'operation', operation.getId());
}
this.client.completeTransaction();
}

}

return InteractiveExplorerControl;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*globals define */

define([
'panels/InteractiveEditor/InteractiveEditorPanel',
'widgets/InteractiveExplorer/InteractiveExplorerWidget',
'./InteractiveExplorerControl',
], function (
InteractiveEditorPanel,
InteractiveExplorerWidget,
InteractiveExplorerControl,
) {
'use strict';

class InteractiveExplorerPanel extends InteractiveEditorPanel {

initialize() {
this.setTitle('');
this.widget = new InteractiveExplorerWidget(this.logger, this.$el);
this.widget.setTitle = title => this.setTitle(title);

this.control = new InteractiveExplorerControl({
logger: this.logger,
client: this._client,
embedded: this._embedded,
widget: this.widget
});

this.onActivate();
}
}

return InteractiveExplorerPanel;
});
Loading

0 comments on commit 5a62fea

Please sign in to comment.