Skip to content

Commit

Permalink
Add Support For 3D Scatter Plots and Line Plots. Closes #1463 (#1572)
Browse files Browse the repository at this point in the history
* Update MetaModel for 3D Objects Support. Closes #1552

* Add Case for Plot3D in PlotlyGraphControl

* Refactor viz controllers and FigureExtractor to get GraphNode from Children

* Add Support For 3D Scatter Plots and Line Plots (Closes #1463)

1. Add support for 3DScatter and LinePlots.
2. Refactor PlotlyDescExtractor to incoroprate3D Subplots
3. Extract 3D plots data from backend_deepforge.py
4. Minor changes to ExectionIndexControl
5. Change plotly.min.js to new version (v1.52.3)
6. Other minor changes

* WIP- Address PR comments and add a pipeline to devProject.webgmex

* Add operation dependency in devProject.webgmex

* WIP SUB_GRAPH -> SubGraph and refactored duplicate code

Co-authored-by: Brian Broll <brian.broll@gmail.com>
  • Loading branch information
umesh-timalsina and brollb authored Apr 3, 2020
1 parent f3d9577 commit 5c3aeb4
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 167 deletions.
57 changes: 36 additions & 21 deletions src/common/viz/FigureExtractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ define(['./Utils'], function (Utils) {
};
const EXTRACTORS = {
GRAPH: 'Graph',
SUBGRAPH: 'SubGraph',
PLOT2D: 'Plot2D',
PLOT3D: 'Plot3D',
IMAGE: 'Image',
Expand All @@ -28,6 +29,12 @@ define(['./Utils'], function (Utils) {
}
};

FigureExtractor.prototype.extractChildrenOfType = function(node, metaType) {
const children = node.getChildrenIds().map(id => this._client.getNode(id));
return children.filter(node => this.getMetaType(node) === metaType)
.map(child => this.extract(child));
};

FigureExtractor.prototype.constructor = FigureExtractor;

FigureExtractor.prototype[EXTRACTORS.GRAPH] = function(node) {
Expand All @@ -54,7 +61,8 @@ define(['./Utils'], function (Utils) {
return desc;
};

FigureExtractor.prototype[EXTRACTORS.PLOT2D] = function (node) {

FigureExtractor.prototype[EXTRACTORS.SUBGRAPH] = function(node){
const id = node.getId(),
graphId = node.getParentId(),
execId = this.getExecutionId(node);
Expand All @@ -63,7 +71,7 @@ define(['./Utils'], function (Utils) {
desc = {
id: id,
execId: execId,
type: 'plot2D',
type: this.getMetaType(node) === EXTRACTORS.PLOT3D ? 'plot3D' : 'plot2D',
graphId: this._client.getNode(graphId).getAttribute('id'),
subgraphId: node.getAttribute('id'),
subgraphName: node.getAttribute('name'),
Expand All @@ -74,20 +82,22 @@ define(['./Utils'], function (Utils) {
ylabel: node.getAttribute('ylabel'),
};

const children = node.getChildrenIds().map(id => this._client.getNode(id));
desc.lines = children.filter(node => this.getMetaType(node) === EXTRACTORS.LINE)
.map(lineNode => this.extract(lineNode));
desc.images = children.filter(node => this.getMetaType(node) === EXTRACTORS.IMAGE)
.map(imageNode => this.extract(imageNode));

desc.scatterPoints = children.filter(node => this.getMetaType(node) === EXTRACTORS.SCATTER_POINTS)
.map(scatterPointsNode => this.extract(scatterPointsNode));
desc.lines = this.extractChildrenOfType(node, EXTRACTORS.LINE);
desc.scatterPoints = this.extractChildrenOfType(node, EXTRACTORS.SCATTER_POINTS);
return desc;
};

FigureExtractor.prototype[EXTRACTORS.PLOT2D] = function (node) {
let desc = this[EXTRACTORS.SUBGRAPH](node);
desc.images = this.extractChildrenOfType(node, EXTRACTORS.IMAGE);
return desc;
};

FigureExtractor.prototype[EXTRACTORS.PLOT3D] = function(/*node*/) {
throw new Error('Not Implemented yet');
FigureExtractor.prototype[EXTRACTORS.PLOT3D] = function(node) {
let desc = this[EXTRACTORS.SUBGRAPH](node);
desc.zlim = node.getAttribute('zlim');
desc.zlabel = node.getAttribute('zlabel');
return desc;
};

FigureExtractor.prototype[EXTRACTORS.LINE] = function (node) {
Expand All @@ -97,22 +107,21 @@ define(['./Utils'], function (Utils) {

points = node.getAttribute('points').split(';')
.filter(data => !!data) // remove any ''
.map(pair => {
const [x, y] = pair.split(',').map(num => parseFloat(num));
return {x, y};
});
.map(pair => extractPointsArray(pair));

desc = {
id: id,
execId: execId,
subgraphId: this._client.getNode(node.getParentId()).getAttribute('id'),
lineName: node.getAttribute('name'),
label: node.getAttribute('label'),
lineWidth: node.getAttribute('lineWidth'),
marker: node.getAttribute('marker'),
name: node.getAttribute('name'),
type: 'line',
points: points,
color: node.getAttribute('color')
};

return desc;
};

Expand Down Expand Up @@ -143,10 +152,7 @@ define(['./Utils'], function (Utils) {

points = node.getAttribute('points').split(';')
.filter(data => !!data) // remove any ''
.map(pair => {
const [x, y] = pair.split(',').map(num => parseFloat(num));
return {x, y};
});
.map(pair => extractPointsArray(pair));
desc = {
id: id,
execId: execId,
Expand Down Expand Up @@ -194,5 +200,14 @@ define(['./Utils'], function (Utils) {
return this._metaNodesMap[metaTypeId];
};

const extractPointsArray = function (pair) {
const pointsArr = pair.split(',').map(num => parseFloat(num));
let cartesianPoint = {x: pointsArr[0], y: pointsArr[1]};
if (pointsArr.length === 3) {
cartesianPoint.z = pointsArr[2];
}
return cartesianPoint;
};

return FigureExtractor;
});
10 changes: 3 additions & 7 deletions src/plugins/ExecuteJob/metadata/Figure.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ define([
state.axes.forEach(axes => {
const axesNode = this.core.createNode({
parent: this.node,
base: Figure.is3D(axes) ? this.META.Plot3D : this.META.Plot2D
base: axes.is3D ? this.META.Plot3D : this.META.Plot2D
});
this.setAxesProperties(axesNode, axes);
this.addAxesLines(axesNode, this.node, axes);
if(!Figure.is3D(axes)){
if(!axes.is3D){
this.addAxesImage(axesNode, this.node, axes);
}
this.addAxesScatterPoints(axesNode, this.node, axes);
Expand All @@ -29,7 +29,7 @@ define([
this.core.setAttribute(axesNode, 'ylabel', axes.ylabel);
this.core.setAttribute(axesNode, 'xlim', axes.xlim);
this.core.setAttribute(axesNode, 'ylim', axes.ylim);
if(Figure.is3D(axes)){
if(axes.is3D){
this.core.setAttribute(axesNode, 'zlabel', axes.zlabel);
this.core.setAttribute(axesNode, 'zlim', axes.zlim);
}
Expand Down Expand Up @@ -93,10 +93,6 @@ define([
return 'Graph';
}

static is3D(axes) {
return !!axes.zlabel;
}

}

return Figure;
Expand Down
32 changes: 28 additions & 4 deletions src/plugins/GenerateJob/templates/backend_deepforge.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import six

import numpy as np
import numpy.ma as ma

from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
Expand All @@ -80,6 +81,8 @@
from matplotlib import transforms, collections
from matplotlib.collections import LineCollection, PathCollection
from matplotlib.path import Path
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Line3D, Path3DCollection
from matplotlib.pyplot import gcf, close
import simplejson as json

Expand Down Expand Up @@ -378,14 +381,24 @@ def figure_to_state(self):
axes_data['ylabel'] = axes.get_ylabel()
axes_data['xlim'] = axes.get_xlim()
axes_data['ylim'] = axes.get_ylim()
axes_data['is3D'] = False
if hasattr(axes, 'get_zlabel'):
axes_data['zlim'] = axes.get_zlim()
axes_data['zlabel'] = axes.get_zlabel()
axes_data['is3D'] = True

axes_data['lines'] = []
axes_data['images'] = []
axes_data['scatterPoints'] = []

# Line Data
for i, line in enumerate(axes.lines):
lineDict = {}
lineDict['points'] = line.get_xydata().tolist()
if isinstance(line, Line3D):
points = line.get_data_3d()
lineDict['points'] = np.transpose(points).tolist()
else:
lineDict['points'] = line.get_xydata().tolist()
lineDict['label'] = ''
lineDict['color'] = to_hex(line.get_color())
lineDict['marker'] = line.get_marker()
Expand All @@ -395,12 +408,12 @@ def figure_to_state(self):
if line.get_label() != default_label:
lineDict['label'] = line.get_label()
axes_data['lines'].append(lineDict)

if lineDict['marker'] is None or lineDict['marker'] == 'None':
lineDict['marker'] = ''
# Line Collections
for collection in axes.collections:
if isinstance(collection, LineCollection):
axes_data['lines'].extend(self.process_line_collection(collection))

if isinstance(collection, PathCollection):
axes_data['scatterPoints'].append(self.process_collection(axes, collection, force_pathtrans=axes.transAxes))

Expand Down Expand Up @@ -464,14 +477,25 @@ def process_collection(self, ax, collection,
offset_dict = {"data": "before",
"screen": "after"}
offset_order = offset_dict[collection.get_offset_position()]
coll_offsets = offsets
if isinstance(collection, Path3DCollection):
coll_offsets = self.get_3d_array(collection._offsets3d)

return {
'color': self.colors_to_hex(styles['facecolor'].tolist()),
'points': offsets.tolist(),
'points': coll_offsets.tolist(),
'marker': '.', #TODO: Detect markers from Paths
'label': '',
'width': self.convert_size_array(collection.get_sizes())
}

def get_3d_array(self, masked_array_tuple):
values = []
for array in masked_array_tuple:
values.append(ma.getdata(array))
return np.transpose(np.asarray(values))


def convert_size_array(self, size_array):
size = [math.sqrt(s) for s in size_array]
if len(size) == 1:
Expand Down
Binary file modified src/seeds/devProject/devProject.webgmex
Binary file not shown.
35 changes: 28 additions & 7 deletions src/visualizers/panels/ExecutionIndex/ExecutionIndexControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,49 @@ define([
};

ExecutionIndexControl.prototype._combineSubGraphsDesc = function (consolidatedDesc, subGraphs, abbr) {
let currentSubGraph, imageSubGraphCopy, added=0;
let currentSubGraph, imageSubGraphCopy, added=0, subgraphCopy;
const originalLength = consolidatedDesc.subGraphs.length;
for (let i = 0; i < originalLength; i++) {
if (!subGraphs[i]) break;
currentSubGraph = consolidatedDesc.subGraphs[i+added];
subGraphs[i].abbr = abbr;
if (subGraphs[i].images.length > 0 || currentSubGraph.images.length > 0) {
imageSubGraphCopy = JSON.parse(JSON.stringify(subGraphs[i]));
imageSubGraphCopy.title = getDisplayTitle(subGraphs[i], true);
consolidatedDesc.subGraphs.splice(i+added, 0, imageSubGraphCopy);

if(subGraphs[i].type !== currentSubGraph.type){
subgraphCopy = JSON.parse(JSON.stringify(subGraphs[i]));
subgraphCopy.title = getDisplayTitle(subGraphs[i], true);
consolidatedDesc.subGraphs.splice(i+added, 0, subgraphCopy);
added++;
continue;
}
if(currentSubGraph.images && subGraphs[i].images) {
if (subGraphs[i].images.length > 0 || currentSubGraph.images.length > 0) {
imageSubGraphCopy = JSON.parse(JSON.stringify(subGraphs[i]));
imageSubGraphCopy.title = getDisplayTitle(subGraphs[i], true);
consolidatedDesc.subGraphs.splice(i+added, 0, imageSubGraphCopy);
added++;
continue;
}
}

currentSubGraph.title += ` vs. ${getDisplayTitle(subGraphs[i], true)}`;
if(currentSubGraph.xlabel !== subGraphs[i].xlabel){
currentSubGraph.xlabel += ` ${subGraphs[i].xlabel}`;
}

if(currentSubGraph.ylabel !== subGraphs[i].ylabel){
currentSubGraph.ylabel += ` ${subGraphs[i].ylabel}`;
}

if(currentSubGraph.zlabel && currentSubGraph.zlabel !== subGraphs[i].zlabel){
currentSubGraph.zlabel += ` ${subGraphs[i].zlabel}`;
}

subGraphs[i].lines.forEach((line, index) => {
let lineClone = JSON.parse(JSON.stringify(line));
lineClone.label = (lineClone.label || `line${index}`) + ` (${abbr})`;
currentSubGraph.lines.push(lineClone);
});

subGraphs[i].scatterPoints.forEach(scatterPoint => {
let scatterClone = JSON.parse(JSON.stringify(scatterPoint));
currentSubGraph.scatterPoints.push(scatterClone);
Expand Down Expand Up @@ -223,10 +244,10 @@ define([
desc,
base,
type;
const graphNode = this.figureExtractor.getGraphNode(node),
isGraphOrChildren = !!graphNode;

if (node) {
const graphNode = this.figureExtractor.getGraphNode(node),
isGraphOrChildren = !!graphNode;
base = this._client.getNode(node.getBaseId());
type = base.getAttribute('name');
desc = {
Expand Down
Loading

0 comments on commit 5c3aeb4

Please sign in to comment.