Skip to content

Commit 5c3aeb4

Browse files
Add Support For 3D Scatter Plots and Line Plots. Closes #1463 (#1572)
* 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>
1 parent f3d9577 commit 5c3aeb4

File tree

8 files changed

+339
-167
lines changed

8 files changed

+339
-167
lines changed

src/common/viz/FigureExtractor.js

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ define(['./Utils'], function (Utils) {
66
};
77
const EXTRACTORS = {
88
GRAPH: 'Graph',
9+
SUBGRAPH: 'SubGraph',
910
PLOT2D: 'Plot2D',
1011
PLOT3D: 'Plot3D',
1112
IMAGE: 'Image',
@@ -28,6 +29,12 @@ define(['./Utils'], function (Utils) {
2829
}
2930
};
3031

32+
FigureExtractor.prototype.extractChildrenOfType = function(node, metaType) {
33+
const children = node.getChildrenIds().map(id => this._client.getNode(id));
34+
return children.filter(node => this.getMetaType(node) === metaType)
35+
.map(child => this.extract(child));
36+
};
37+
3138
FigureExtractor.prototype.constructor = FigureExtractor;
3239

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

57-
FigureExtractor.prototype[EXTRACTORS.PLOT2D] = function (node) {
64+
65+
FigureExtractor.prototype[EXTRACTORS.SUBGRAPH] = function(node){
5866
const id = node.getId(),
5967
graphId = node.getParentId(),
6068
execId = this.getExecutionId(node);
@@ -63,7 +71,7 @@ define(['./Utils'], function (Utils) {
6371
desc = {
6472
id: id,
6573
execId: execId,
66-
type: 'plot2D',
74+
type: this.getMetaType(node) === EXTRACTORS.PLOT3D ? 'plot3D' : 'plot2D',
6775
graphId: this._client.getNode(graphId).getAttribute('id'),
6876
subgraphId: node.getAttribute('id'),
6977
subgraphName: node.getAttribute('name'),
@@ -74,20 +82,22 @@ define(['./Utils'], function (Utils) {
7482
ylabel: node.getAttribute('ylabel'),
7583
};
7684

77-
const children = node.getChildrenIds().map(id => this._client.getNode(id));
78-
desc.lines = children.filter(node => this.getMetaType(node) === EXTRACTORS.LINE)
79-
.map(lineNode => this.extract(lineNode));
80-
desc.images = children.filter(node => this.getMetaType(node) === EXTRACTORS.IMAGE)
81-
.map(imageNode => this.extract(imageNode));
82-
83-
desc.scatterPoints = children.filter(node => this.getMetaType(node) === EXTRACTORS.SCATTER_POINTS)
84-
.map(scatterPointsNode => this.extract(scatterPointsNode));
85+
desc.lines = this.extractChildrenOfType(node, EXTRACTORS.LINE);
86+
desc.scatterPoints = this.extractChildrenOfType(node, EXTRACTORS.SCATTER_POINTS);
87+
return desc;
88+
};
8589

90+
FigureExtractor.prototype[EXTRACTORS.PLOT2D] = function (node) {
91+
let desc = this[EXTRACTORS.SUBGRAPH](node);
92+
desc.images = this.extractChildrenOfType(node, EXTRACTORS.IMAGE);
8693
return desc;
8794
};
8895

89-
FigureExtractor.prototype[EXTRACTORS.PLOT3D] = function(/*node*/) {
90-
throw new Error('Not Implemented yet');
96+
FigureExtractor.prototype[EXTRACTORS.PLOT3D] = function(node) {
97+
let desc = this[EXTRACTORS.SUBGRAPH](node);
98+
desc.zlim = node.getAttribute('zlim');
99+
desc.zlabel = node.getAttribute('zlabel');
100+
return desc;
91101
};
92102

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

98108
points = node.getAttribute('points').split(';')
99109
.filter(data => !!data) // remove any ''
100-
.map(pair => {
101-
const [x, y] = pair.split(',').map(num => parseFloat(num));
102-
return {x, y};
103-
});
110+
.map(pair => extractPointsArray(pair));
111+
104112
desc = {
105113
id: id,
106114
execId: execId,
107115
subgraphId: this._client.getNode(node.getParentId()).getAttribute('id'),
108116
lineName: node.getAttribute('name'),
109117
label: node.getAttribute('label'),
118+
lineWidth: node.getAttribute('lineWidth'),
119+
marker: node.getAttribute('marker'),
110120
name: node.getAttribute('name'),
111121
type: 'line',
112122
points: points,
113123
color: node.getAttribute('color')
114124
};
115-
116125
return desc;
117126
};
118127

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

144153
points = node.getAttribute('points').split(';')
145154
.filter(data => !!data) // remove any ''
146-
.map(pair => {
147-
const [x, y] = pair.split(',').map(num => parseFloat(num));
148-
return {x, y};
149-
});
155+
.map(pair => extractPointsArray(pair));
150156
desc = {
151157
id: id,
152158
execId: execId,
@@ -194,5 +200,14 @@ define(['./Utils'], function (Utils) {
194200
return this._metaNodesMap[metaTypeId];
195201
};
196202

203+
const extractPointsArray = function (pair) {
204+
const pointsArr = pair.split(',').map(num => parseFloat(num));
205+
let cartesianPoint = {x: pointsArr[0], y: pointsArr[1]};
206+
if (pointsArr.length === 3) {
207+
cartesianPoint.z = pointsArr[2];
208+
}
209+
return cartesianPoint;
210+
};
211+
197212
return FigureExtractor;
198213
});

src/plugins/ExecuteJob/metadata/Figure.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ define([
1212
state.axes.forEach(axes => {
1313
const axesNode = this.core.createNode({
1414
parent: this.node,
15-
base: Figure.is3D(axes) ? this.META.Plot3D : this.META.Plot2D
15+
base: axes.is3D ? this.META.Plot3D : this.META.Plot2D
1616
});
1717
this.setAxesProperties(axesNode, axes);
1818
this.addAxesLines(axesNode, this.node, axes);
19-
if(!Figure.is3D(axes)){
19+
if(!axes.is3D){
2020
this.addAxesImage(axesNode, this.node, axes);
2121
}
2222
this.addAxesScatterPoints(axesNode, this.node, axes);
@@ -29,7 +29,7 @@ define([
2929
this.core.setAttribute(axesNode, 'ylabel', axes.ylabel);
3030
this.core.setAttribute(axesNode, 'xlim', axes.xlim);
3131
this.core.setAttribute(axesNode, 'ylim', axes.ylim);
32-
if(Figure.is3D(axes)){
32+
if(axes.is3D){
3333
this.core.setAttribute(axesNode, 'zlabel', axes.zlabel);
3434
this.core.setAttribute(axesNode, 'zlim', axes.zlim);
3535
}
@@ -93,10 +93,6 @@ define([
9393
return 'Graph';
9494
}
9595

96-
static is3D(axes) {
97-
return !!axes.zlabel;
98-
}
99-
10096
}
10197

10298
return Figure;

src/plugins/GenerateJob/templates/backend_deepforge.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import six
7272

7373
import numpy as np
74+
import numpy.ma as ma
7475

7576
from matplotlib._pylab_helpers import Gcf
7677
from matplotlib.backend_bases import (
@@ -80,6 +81,8 @@
8081
from matplotlib import transforms, collections
8182
from matplotlib.collections import LineCollection, PathCollection
8283
from matplotlib.path import Path
84+
from mpl_toolkits.mplot3d import Axes3D
85+
from mpl_toolkits.mplot3d.art3d import Line3D, Path3DCollection
8386
from matplotlib.pyplot import gcf, close
8487
import simplejson as json
8588

@@ -378,14 +381,24 @@ def figure_to_state(self):
378381
axes_data['ylabel'] = axes.get_ylabel()
379382
axes_data['xlim'] = axes.get_xlim()
380383
axes_data['ylim'] = axes.get_ylim()
384+
axes_data['is3D'] = False
385+
if hasattr(axes, 'get_zlabel'):
386+
axes_data['zlim'] = axes.get_zlim()
387+
axes_data['zlabel'] = axes.get_zlabel()
388+
axes_data['is3D'] = True
389+
381390
axes_data['lines'] = []
382391
axes_data['images'] = []
383392
axes_data['scatterPoints'] = []
384393

385394
# Line Data
386395
for i, line in enumerate(axes.lines):
387396
lineDict = {}
388-
lineDict['points'] = line.get_xydata().tolist()
397+
if isinstance(line, Line3D):
398+
points = line.get_data_3d()
399+
lineDict['points'] = np.transpose(points).tolist()
400+
else:
401+
lineDict['points'] = line.get_xydata().tolist()
389402
lineDict['label'] = ''
390403
lineDict['color'] = to_hex(line.get_color())
391404
lineDict['marker'] = line.get_marker()
@@ -395,12 +408,12 @@ def figure_to_state(self):
395408
if line.get_label() != default_label:
396409
lineDict['label'] = line.get_label()
397410
axes_data['lines'].append(lineDict)
398-
411+
if lineDict['marker'] is None or lineDict['marker'] == 'None':
412+
lineDict['marker'] = ''
399413
# Line Collections
400414
for collection in axes.collections:
401415
if isinstance(collection, LineCollection):
402416
axes_data['lines'].extend(self.process_line_collection(collection))
403-
404417
if isinstance(collection, PathCollection):
405418
axes_data['scatterPoints'].append(self.process_collection(axes, collection, force_pathtrans=axes.transAxes))
406419

@@ -464,14 +477,25 @@ def process_collection(self, ax, collection,
464477
offset_dict = {"data": "before",
465478
"screen": "after"}
466479
offset_order = offset_dict[collection.get_offset_position()]
480+
coll_offsets = offsets
481+
if isinstance(collection, Path3DCollection):
482+
coll_offsets = self.get_3d_array(collection._offsets3d)
483+
467484
return {
468485
'color': self.colors_to_hex(styles['facecolor'].tolist()),
469-
'points': offsets.tolist(),
486+
'points': coll_offsets.tolist(),
470487
'marker': '.', #TODO: Detect markers from Paths
471488
'label': '',
472489
'width': self.convert_size_array(collection.get_sizes())
473490
}
474491

492+
def get_3d_array(self, masked_array_tuple):
493+
values = []
494+
for array in masked_array_tuple:
495+
values.append(ma.getdata(array))
496+
return np.transpose(np.asarray(values))
497+
498+
475499
def convert_size_array(self, size_array):
476500
size = [math.sqrt(s) for s in size_array]
477501
if len(size) == 1:
12.1 KB
Binary file not shown.

src/visualizers/panels/ExecutionIndex/ExecutionIndexControl.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,28 +117,49 @@ define([
117117
};
118118

119119
ExecutionIndexControl.prototype._combineSubGraphsDesc = function (consolidatedDesc, subGraphs, abbr) {
120-
let currentSubGraph, imageSubGraphCopy, added=0;
120+
let currentSubGraph, imageSubGraphCopy, added=0, subgraphCopy;
121121
const originalLength = consolidatedDesc.subGraphs.length;
122122
for (let i = 0; i < originalLength; i++) {
123123
if (!subGraphs[i]) break;
124124
currentSubGraph = consolidatedDesc.subGraphs[i+added];
125125
subGraphs[i].abbr = abbr;
126-
if (subGraphs[i].images.length > 0 || currentSubGraph.images.length > 0) {
127-
imageSubGraphCopy = JSON.parse(JSON.stringify(subGraphs[i]));
128-
imageSubGraphCopy.title = getDisplayTitle(subGraphs[i], true);
129-
consolidatedDesc.subGraphs.splice(i+added, 0, imageSubGraphCopy);
126+
127+
if(subGraphs[i].type !== currentSubGraph.type){
128+
subgraphCopy = JSON.parse(JSON.stringify(subGraphs[i]));
129+
subgraphCopy.title = getDisplayTitle(subGraphs[i], true);
130+
consolidatedDesc.subGraphs.splice(i+added, 0, subgraphCopy);
130131
added++;
131132
continue;
132133
}
134+
if(currentSubGraph.images && subGraphs[i].images) {
135+
if (subGraphs[i].images.length > 0 || currentSubGraph.images.length > 0) {
136+
imageSubGraphCopy = JSON.parse(JSON.stringify(subGraphs[i]));
137+
imageSubGraphCopy.title = getDisplayTitle(subGraphs[i], true);
138+
consolidatedDesc.subGraphs.splice(i+added, 0, imageSubGraphCopy);
139+
added++;
140+
continue;
141+
}
142+
}
133143

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

149+
if(currentSubGraph.ylabel !== subGraphs[i].ylabel){
150+
currentSubGraph.ylabel += ` ${subGraphs[i].ylabel}`;
151+
}
152+
153+
if(currentSubGraph.zlabel && currentSubGraph.zlabel !== subGraphs[i].zlabel){
154+
currentSubGraph.zlabel += ` ${subGraphs[i].zlabel}`;
155+
}
136156

137157
subGraphs[i].lines.forEach((line, index) => {
138158
let lineClone = JSON.parse(JSON.stringify(line));
139159
lineClone.label = (lineClone.label || `line${index}`) + ` (${abbr})`;
140160
currentSubGraph.lines.push(lineClone);
141161
});
162+
142163
subGraphs[i].scatterPoints.forEach(scatterPoint => {
143164
let scatterClone = JSON.parse(JSON.stringify(scatterPoint));
144165
currentSubGraph.scatterPoints.push(scatterClone);
@@ -223,10 +244,10 @@ define([
223244
desc,
224245
base,
225246
type;
226-
const graphNode = this.figureExtractor.getGraphNode(node),
227-
isGraphOrChildren = !!graphNode;
228247

229248
if (node) {
249+
const graphNode = this.figureExtractor.getGraphNode(node),
250+
isGraphOrChildren = !!graphNode;
230251
base = this._client.getNode(node.getBaseId());
231252
type = base.getAttribute('name');
232253
desc = {

0 commit comments

Comments
 (0)