Skip to content

Commit

Permalink
Add initial version of TensorPlotter. Closes #1692 (#1694)
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

* unregister action on destroy

* Remove OperationTemplate

* Clean up python commands and add dialog on error

* Ensure that name,title are set on save

* refactor executing python code

* Improve error dialog

* Misc code cleanup

* Skip simple grid lib in linting

* Fix quotes in tests
  • Loading branch information
brollb committed Jul 27, 2020
1 parent 14288bf commit 10f5d13
Show file tree
Hide file tree
Showing 24 changed files with 3,406 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ exclude_paths:
- src/visualizers/widgets/PipelineEditor/klay.js
- src/visualizers/widgets/PlotlyGraph/lib/plotly.min.js
- src/plugins/GenerateJob/templates/utils.build.js
- src/visualizers/widgets/TensorPlotter/lib/
- src/visualizers/widgets/TensorPlotter/styles/simple-grid.min.css
14 changes: 13 additions & 1 deletion src/visualizers/Visualizers.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,17 @@
"title": "OperationDepEditor",
"panel": "panels/OperationDepEditor/OperationDepEditorPanel",
"DEBUG_ONLY": false
},
{
"id": "TensorPlotter",
"title": "TensorPlotter",
"panel": "panels/TensorPlotter/TensorPlotterPanel",
"DEBUG_ONLY": false
},
{
"id": "InteractiveExplorer",
"title": "InteractiveExplorer",
"panel": "panels/InteractiveExplorer/InteractiveExplorerPanel",
"DEBUG_ONLY": false
}
]
]
42 changes: 42 additions & 0 deletions src/visualizers/panels/TensorPlotter/TensorPlotterControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*globals define */
/**
* Generated by VisualizerGenerator 1.7.0 from webgme on Mon May 04 2020 17:09:31 GMT-0500 (Central Daylight Time).
*/

define([
'panels/InteractiveExplorer/InteractiveExplorerControl',
], function (
InteractiveExplorerControl,
) {

'use strict';

class TensorPlotterControl extends InteractiveExplorerControl {

getObjectDescriptor(nodeId) {
const desc = super.getObjectDescriptor(nodeId);

if (desc) {
const node = this.client.getNode(nodeId);
desc.data = node.getAttribute('data');
desc.type = node.getAttribute('type');
}

return desc;
}

getTerritory(nodeId) {
const territory = {};
const node = this.client.getNode(nodeId);
const parentId = node.getParentId();
territory[parentId] = {children: 1};

const omitParentNode = event => event.eid !== parentId;
this.territoryEventFilters = [omitParentNode];

return territory;
}
}

return TensorPlotterControl;
});
101 changes: 101 additions & 0 deletions src/visualizers/panels/TensorPlotter/TensorPlotterPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*globals define, _, WebGMEGlobal*/
/**
* Generated by VisualizerGenerator 1.7.0 from webgme on Mon May 04 2020 17:09:31 GMT-0500 (Central Daylight Time).
*/

define([
'js/PanelBase/PanelBaseWithHeader',
'js/PanelManager/IActivePanel',
'widgets/TensorPlotter/TensorPlotterWidget',
'./TensorPlotterControl'
], function (
PanelBaseWithHeader,
IActivePanel,
TensorPlotterWidget,
TensorPlotterControl
) {
'use strict';

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

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

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

//initialize UI
this._initialize();

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

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

TensorPlotterPanel.prototype._initialize = function () {
var self = this;

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

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

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

this.control = new TensorPlotterControl({
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 */
TensorPlotterPanel.prototype.onReadOnlyChanged = function (isReadOnly) {
//apply parent's onReadOnlyChanged
PanelBaseWithHeader.prototype.onReadOnlyChanged.call(this, isReadOnly);

};

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

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

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

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

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

return TensorPlotterPanel;
});
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ define([
DeepForge.unregisterAction('Save');
}
}

updateNode(/*desc*/) {
}
}

return InteractiveEditorWidget;
Expand Down
6 changes: 6 additions & 0 deletions src/visualizers/widgets/TensorPlotter/ArtifactLoader.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<h4>Available Artifacts</h4>
<ul class="list-group artifacts">
<li class="list-group-item">Artifact
<span class="glyphicon glyphicon-remove pull-right" aria-hidden="true"></span>
</li>
</ul>
137 changes: 137 additions & 0 deletions src/visualizers/widgets/TensorPlotter/ArtifactLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*globals define, $*/
define([
'deepforge/EventEmitter',
'deepforge/storage/index',
'underscore',
'text!./ArtifactLoader.html',
'css!./styles/ArtifactLoader.css',
], function(
EventEmitter,
Storage,
_,
Html,
) {
class ArtifactLoader extends EventEmitter {
constructor(container) {
super();
this.session = null;
this.$el = container;
this.$el.addClass('artifact-loader');
this.$el.append($(Html));
this.$artifacts = this.$el.find('.artifacts');
this.artifacts = [];
this.render = _.debounce(this.render.bind(this), 250);
}

register(desc) {
this.artifacts.push(new Artifact(desc));
this.render();
}

unregister(artifactId) {
const index = this.artifacts.findIndex(artifact => artifact.id === artifactId);
if (index > -1) {
this.artifacts.splice(index, 1);
this.render();
}
}

async load(artifact) {
const desc = artifact.data;
const dataInfo = JSON.parse(desc.data);
const config = await this.getAuthenticationConfig(dataInfo);

const pyName = desc.name.replace(/\..*$/, '');
const loading = this.session.addArtifact(pyName, dataInfo, desc.type, config);
artifact.state = ArtifactState.LOADING;
this.render();
await loading;
artifact.state = ArtifactState.LOADED;
this.render();
this.emit('load', desc);
}

async getAuthenticationConfig (dataInfo) {
const {backend} = dataInfo;
const metadata = Storage.getStorageMetadata(backend);
metadata.configStructure = metadata.configStructure
.filter(option => option.isAuth);

if (metadata.configStructure.length) {
const configDialog = this.getConfigDialog();
const title = `Authenticate with ${metadata.name}`;
const iconClass = `glyphicon glyphicon-download-alt`;
const config = await configDialog.show(metadata, {title, iconClass});

return config[backend];
}
}

render() {
this.$artifacts.empty();
this.artifacts.forEach(artifact => {
const $element = artifact.element();
if (artifact.state === ArtifactState.NOT_LOADED) {
$element.on('click', event => {
event.stopPropagation();
event.preventDefault();
this.load(artifact);
});
}
this.$artifacts.append($element);
});
}
}

class ArtifactState {
constructor(clazz, text, icon) {
this.class = clazz || '';
this.text = text || '';
this.icon = icon || '';
}

configure($element) {
if (this.text) {
const $state = $('<span>', {class: 'pull-right artifact-state'});
$state.text(this.text);
$element.append($state);
}
if (this.class) {
$element.addClass(this.class);
}
if (this.icon) {
const $icon = glyph(this.icon);
$element.append($icon);
}
}
}

ArtifactState.LOADING = new ArtifactState('list-group-item-warning', 'Loading...');
ArtifactState.NOT_LOADED = new ArtifactState(null, null, 'upload');
ArtifactState.LOADED = new ArtifactState('list-group-item-success', 'Available');

class Artifact {
constructor(data) {
this.id = data.id;
this.data = data;
this.state = ArtifactState.NOT_LOADED;
}

element() {
const $element = $('<li>', {class: 'list-group-item'});
$element.text(this.data.name);

this.state.configure($element);

return $element;
}
}

function glyph(name) {
const $el = $('<span>', {class: `glyphicon glyphicon-${name} pull-right`});
$el.attr('aria-hidden', true);
return $el;
}

return ArtifactLoader;
});
51 changes: 51 additions & 0 deletions src/visualizers/widgets/TensorPlotter/DataEditorBase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* globals define, $ */
define([
'underscore',
'deepforge/EventEmitter',
], function(
_,
EventEmitter,
) {

class DataEditorBase extends EventEmitter {
constructor(html, dataFields, updateOnChange) {
super();
this.$el = $(html);

this.$elements = {};
dataFields.forEach(name => {
this.$elements[name] = this.$el.find(`#${name}`);
if (updateOnChange) {
this.$elements[name].change(() => this.onUpdate());
}
});
}

set(values) {
Object.entries(this.$elements).map(entry => {
const [name, element] = entry;
if (values.hasOwnProperty(name)) {
element.val(values[name]);
}
});
}

data() {
const entries = Object.entries(this.$elements)
.map(entry => {
const [name, element] = entry;
const value = element.val();
return [name, value];
})
.filter(entry => !!entry[1]);
return _.object(entries);
}

onUpdate() {
const values = this.data();
this.emit('update', values);
}
}

return DataEditorBase;
});
18 changes: 18 additions & 0 deletions src/visualizers/widgets/TensorPlotter/PlotEditor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<form>
<div class="form-group">
<label for="title">Title</label>
<input type="text", class="form-control" id="title"/>
</div>
<div class="form-group">
<label for="xaxis">X Axis Label</label>
<input type="text", class="form-control" id="xaxis"/>
</div>
<div class="form-group">
<label for="yaxis">Y Axis Label</label>
<input type="text", class="form-control" id="yaxis"/>
</div>
<h4>Plotted Data</h4>
<ul class="list-group plotted-data">
</ul>
<button class="btn btn-primary">Add Data</button>
</form>

0 comments on commit 10f5d13

Please sign in to comment.