Skip to content

Commit 1203f10

Browse files
committed
Frame merging logic
1 parent 01b9287 commit 1203f10

File tree

5 files changed

+274
-82
lines changed

5 files changed

+274
-82
lines changed

src/lib/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,10 @@ lib.objectFromPath = function(path, value) {
546546
* Iterate through an object in-place, converting dotted properties to objects.
547547
*
548548
* @example
549-
* lib.expandObjectPaths('nested.test[2].path', 'value');
550-
* // returns { nested: { test: [null, null, { path: 'value' }]}
551-
*
549+
* lib.expandObjectPaths({'nested.test.path': 'value'});
550+
* // returns { nested: { test: {path: 'value'}}}
552551
*/
552+
553553
// Store this to avoid recompiling regex on every prop since this may happen many
554554
// many times for animations.
555555
// TODO: Premature optimization? Remove?

src/lib/merge_frames.js

-31
This file was deleted.

src/plots/plots.js

+83
Original file line numberDiff line numberDiff line change
@@ -1181,3 +1181,86 @@ plots.modifyFrames = function(gd, operations) {
11811181

11821182
return Promise.resolve();
11831183
};
1184+
1185+
/*
1186+
* Compute a keyframe. Merge a keyframe into its base frame(s) and
1187+
* expand properties.
1188+
*
1189+
* @param {object} frame
1190+
* The keyframe to be computed
1191+
*
1192+
* Returns: a third object with the merged content
1193+
*/
1194+
plots.computeFrame = function(gd, frameName) {
1195+
var i, traceIndices, traceIndex, expandedObj, destIndex;
1196+
var _hash = gd._frameData._frameHash;
1197+
1198+
var framePtr = _hash[frameName];
1199+
1200+
// Return false if the name is invalid:
1201+
if (!framePtr) {
1202+
return false;
1203+
}
1204+
1205+
var frameStack = [framePtr];
1206+
var frameNameStack = [framePtr.name];
1207+
1208+
// Follow frame pointers:
1209+
while((framePtr = gd._frameData._frameHash[framePtr.baseFrame])) {;
1210+
// Avoid infinite loops:
1211+
if(frameNameStack.indexOf(framePtr.name) !== -1) break;
1212+
1213+
frameStack.push(framePtr);
1214+
frameNameStack.push(framePtr.name);
1215+
}
1216+
1217+
// A new object for the merged result:
1218+
var result = {};
1219+
1220+
// Merge, starting with the last and ending with the desired frame:
1221+
while((framePtr = frameStack.pop())) {
1222+
if(framePtr.layout) {
1223+
expandedObj = Lib.expandObjectPaths(framePtr.layout);
1224+
result.layout = Lib.extendDeepNoArrays(result.layout || {}, expandedObj);
1225+
}
1226+
1227+
if(framePtr.data) {
1228+
if(!result.data) {
1229+
result.data = [];
1230+
}
1231+
traceIndices = framePtr.traceIndices;
1232+
1233+
if(!traceIndices) {
1234+
// If not defined, assume serial order starting at zero
1235+
traceIndices = [];
1236+
for(i = 0; i < framePtr.data.length; i++) {
1237+
traceIndices[i] = i;
1238+
}
1239+
}
1240+
1241+
if(!result.traceIndices) {
1242+
result.traceIndices = [];
1243+
}
1244+
1245+
for(i = 0; i < framePtr.data.length; i++) {
1246+
// Loop through this frames data, find out where it should go,
1247+
// and merge it!
1248+
traceIndex = traceIndices[i];
1249+
if(traceIndex === undefined || traceIndex === null) {
1250+
continue;
1251+
}
1252+
1253+
destIndex = result.traceIndices.indexOf(traceIndex);
1254+
if (destIndex === -1) {
1255+
destIndex = result.data.length;
1256+
result.traceIndices[destIndex] = traceIndex;
1257+
}
1258+
1259+
expandedObj = Lib.expandObjectPaths(framePtr.data[i]);
1260+
result.data[destIndex] = Lib.extendDeepNoArrays(result.data[destIndex] || {}, expandedObj);
1261+
}
1262+
}
1263+
}
1264+
1265+
return result;
1266+
};
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
var Plotly = require('@lib/index');
2+
3+
var createGraphDiv = require('../assets/create_graph_div');
4+
var destroyGraphDiv = require('../assets/destroy_graph_div');
5+
var fail = require('../assets/fail_test');
6+
var computeFrame = require('@src/plots/plots').computeFrame;
7+
var Lib = require('@src/lib');
8+
9+
describe('Test mergeFrames', function() {
10+
'use strict';
11+
12+
var gd, mock, f, h;
13+
14+
beforeEach(function(done) {
15+
mock = [{x: [1, 2, 3], y: [2, 1, 3]}, {x: [1, 2, 3], y: [6, 4, 5]}];
16+
gd = createGraphDiv();
17+
Plotly.plot(gd, mock).then(function() {
18+
f = gd._frameData._frames;
19+
h = gd._frameData._frameHash;
20+
}).then(done);
21+
});
22+
23+
afterEach(destroyGraphDiv);
24+
25+
describe('computing a single frame', function() {
26+
var frame1;
27+
28+
beforeEach(function(done) {
29+
frame1 = {
30+
name: 'frame1',
31+
data: [{'marker.size': 8, marker: {color: 'red'}}]
32+
};
33+
34+
Plotly.addFrames(gd, [frame1]).then(done);
35+
});
36+
37+
it('returns false if the frame does not exist', function() {
38+
expect(computeFrame(gd, 'frame8')).toBe(false);
39+
});
40+
41+
it('returns a new object', function() {
42+
expect(computeFrame(gd, 'frame1')).not.toBe(frame1);
43+
});
44+
45+
it('computes a single frame', function() {
46+
var computed = computeFrame(gd, 'frame1')
47+
var expected = {data: [{marker: {size: 8, color: 'red'}}], traceIndices: [0]};
48+
expect(computed).toEqual(expected);
49+
});
50+
});
51+
52+
describe('circularly defined frames', function() {
53+
var frames, results;
54+
55+
beforeEach(function(done) {
56+
frames = [
57+
{name: 'frame0', baseFrame: 'frame1', data: [{'marker.size': 0}]},
58+
{name: 'frame1', baseFrame: 'frame2', data: [{'marker.size': 1}]},
59+
{name: 'frame2', baseFrame: 'frame0', data: [{'marker.size': 2}]}
60+
];
61+
62+
results = [
63+
{traceIndices: [0], data: [{marker: {size: 0}}]},
64+
{traceIndices: [0], data: [{marker: {size: 1}}]},
65+
{traceIndices: [0], data: [{marker: {size: 2}}]}
66+
]
67+
68+
Plotly.addFrames(gd, frames).then(done);
69+
});
70+
71+
for (var ii = 0; ii < 3; ii++) {
72+
(function(i) {
73+
it('avoid infinite recursion (starting point = ' + i + ')', function() {
74+
var result = computeFrame(gd, 'frame' + i);
75+
expect(result).toEqual(results[i]);
76+
});
77+
}(ii));
78+
}
79+
});
80+
81+
describe('computing trace data', function() {
82+
var frames, results;
83+
84+
beforeEach(function(done) {
85+
frames = [{
86+
name: 'frame0',
87+
data: [{'marker.size': 0}],
88+
traceIndices: [2]
89+
}, {
90+
name: 'frame1',
91+
data: [{'marker.size': 1}],
92+
traceIndices: [8]
93+
}, {
94+
name: 'frame2',
95+
data: [{'marker.size': 2}],
96+
traceIndices: [2]
97+
}, {
98+
name: 'frame3',
99+
data: [{'marker.size': 3}, {'marker.size': 4}],
100+
traceIndices: [2, 8]
101+
}, {
102+
name: 'frame4',
103+
data: [
104+
{'marker.size': 5},
105+
{'marker.size': 6},
106+
{'marker.size': 7}
107+
]
108+
}];
109+
110+
Plotly.addFrames(gd, frames).then(done);
111+
});
112+
113+
it('merges orthogonal traces', function() {
114+
frames[0].baseFrame = frames[1].name;
115+
var result = computeFrame(gd, 'frame0');
116+
expect(result).toEqual({
117+
traceIndices: [8, 2],
118+
data: [
119+
{marker: {size: 1}},
120+
{marker: {size: 0}}
121+
]
122+
});
123+
});
124+
125+
it('merges overlapping traces', function() {
126+
frames[0].baseFrame = frames[2].name;
127+
var result = computeFrame(gd, 'frame0');
128+
expect(result).toEqual({
129+
traceIndices: [2],
130+
data: [{marker: {size: 0}}]
131+
});
132+
});
133+
134+
it('merges partially overlapping traces', function() {
135+
frames[0].baseFrame = frames[1].name;
136+
frames[1].baseFrame = frames[2].name;
137+
frames[2].baseFrame = frames[3].name;
138+
var result = computeFrame(gd, 'frame0');
139+
expect(result).toEqual({
140+
traceIndices: [2, 8],
141+
data: [
142+
{marker: {size: 0}},
143+
{marker: {size: 1}}
144+
]
145+
});
146+
});
147+
148+
it('assumes serial order without traceIndices specified', function() {
149+
frames[4].baseFrame = frames[3].name;
150+
var result = computeFrame(gd, 'frame4');
151+
expect(result).toEqual({
152+
traceIndices: [2, 8, 0, 1],
153+
data: [
154+
{marker: {size: 7}},
155+
{marker: {size: 4}},
156+
{marker: {size: 5}},
157+
{marker: {size: 6}}
158+
]
159+
});
160+
});
161+
});
162+
163+
describe('computing trace layout', function() {
164+
var frames, results;
165+
166+
beforeEach(function(done) {
167+
frames = [{
168+
name: 'frame0',
169+
layout: {'margin.l': 40},
170+
}, {
171+
name: 'frame1',
172+
layout: {'margin.l': 80},
173+
}];
174+
175+
Plotly.addFrames(gd, frames).then(done);
176+
});
177+
178+
it('merges layouts', function() {
179+
frames[0].baseFrame = frames[1].name;
180+
var result = computeFrame(gd, 'frame0');
181+
182+
expect(result).toEqual({
183+
layout: {margin: {l: 40}}
184+
});
185+
});
186+
187+
});
188+
});

test/jasmine/tests/merge_frame_test.js

-48
This file was deleted.

0 commit comments

Comments
 (0)