Skip to content

Commit 42467ec

Browse files
committed
Frame merging logic
1 parent 01b9287 commit 42467ec

File tree

5 files changed

+271
-82
lines changed

5 files changed

+271
-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+
};
+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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 computeFrame = require('@src/plots/plots').computeFrame;
6+
7+
describe('Test mergeFrames', function() {
8+
'use strict';
9+
10+
var gd, mock;
11+
12+
beforeEach(function(done) {
13+
mock = [{x: [1, 2, 3], y: [2, 1, 3]}, {x: [1, 2, 3], y: [6, 4, 5]}];
14+
gd = createGraphDiv();
15+
Plotly.plot(gd, mock).then(done);
16+
});
17+
18+
afterEach(destroyGraphDiv);
19+
20+
describe('computing a single frame', function() {
21+
var frame1;
22+
23+
beforeEach(function(done) {
24+
frame1 = {
25+
name: 'frame1',
26+
data: [{'marker.size': 8, marker: {color: 'red'}}]
27+
};
28+
29+
Plotly.addFrames(gd, [frame1]).then(done);
30+
});
31+
32+
it('returns false if the frame does not exist', function() {
33+
expect(computeFrame(gd, 'frame8')).toBe(false);
34+
});
35+
36+
it('returns a new object', function() {
37+
expect(computeFrame(gd, 'frame1')).not.toBe(frame1);
38+
});
39+
40+
it('computes a single frame', function() {
41+
var computed = computeFrame(gd, 'frame1');
42+
var expected = {data: [{marker: {size: 8, color: 'red'}}], traceIndices: [0]};
43+
expect(computed).toEqual(expected);
44+
});
45+
});
46+
47+
describe('circularly defined frames', function() {
48+
var frames, results;
49+
50+
beforeEach(function(done) {
51+
frames = [
52+
{name: 'frame0', baseFrame: 'frame1', data: [{'marker.size': 0}]},
53+
{name: 'frame1', baseFrame: 'frame2', data: [{'marker.size': 1}]},
54+
{name: 'frame2', baseFrame: 'frame0', data: [{'marker.size': 2}]}
55+
];
56+
57+
results = [
58+
{traceIndices: [0], data: [{marker: {size: 0}}]},
59+
{traceIndices: [0], data: [{marker: {size: 1}}]},
60+
{traceIndices: [0], data: [{marker: {size: 2}}]}
61+
];
62+
63+
Plotly.addFrames(gd, frames).then(done);
64+
});
65+
66+
function doTest(i) {
67+
it('avoid infinite recursion (starting point = ' + i + ')', function() {
68+
var result = computeFrame(gd, 'frame' + i);
69+
expect(result).toEqual(results[i]);
70+
});
71+
}
72+
73+
for(var ii = 0; ii < 3; ii++) {
74+
doTest(ii);
75+
}
76+
});
77+
78+
describe('computing trace data', function() {
79+
var frames;
80+
81+
beforeEach(function(done) {
82+
frames = [{
83+
name: 'frame0',
84+
data: [{'marker.size': 0}],
85+
traceIndices: [2]
86+
}, {
87+
name: 'frame1',
88+
data: [{'marker.size': 1}],
89+
traceIndices: [8]
90+
}, {
91+
name: 'frame2',
92+
data: [{'marker.size': 2}],
93+
traceIndices: [2]
94+
}, {
95+
name: 'frame3',
96+
data: [{'marker.size': 3}, {'marker.size': 4}],
97+
traceIndices: [2, 8]
98+
}, {
99+
name: 'frame4',
100+
data: [
101+
{'marker.size': 5},
102+
{'marker.size': 6},
103+
{'marker.size': 7}
104+
]
105+
}];
106+
107+
Plotly.addFrames(gd, frames).then(done);
108+
});
109+
110+
it('merges orthogonal traces', function() {
111+
frames[0].baseFrame = frames[1].name;
112+
var result = computeFrame(gd, 'frame0');
113+
expect(result).toEqual({
114+
traceIndices: [8, 2],
115+
data: [
116+
{marker: {size: 1}},
117+
{marker: {size: 0}}
118+
]
119+
});
120+
});
121+
122+
it('merges overlapping traces', function() {
123+
frames[0].baseFrame = frames[2].name;
124+
var result = computeFrame(gd, 'frame0');
125+
expect(result).toEqual({
126+
traceIndices: [2],
127+
data: [{marker: {size: 0}}]
128+
});
129+
});
130+
131+
it('merges partially overlapping traces', function() {
132+
frames[0].baseFrame = frames[1].name;
133+
frames[1].baseFrame = frames[2].name;
134+
frames[2].baseFrame = frames[3].name;
135+
var result = computeFrame(gd, 'frame0');
136+
expect(result).toEqual({
137+
traceIndices: [2, 8],
138+
data: [
139+
{marker: {size: 0}},
140+
{marker: {size: 1}}
141+
]
142+
});
143+
});
144+
145+
it('assumes serial order without traceIndices specified', function() {
146+
frames[4].baseFrame = frames[3].name;
147+
var result = computeFrame(gd, 'frame4');
148+
expect(result).toEqual({
149+
traceIndices: [2, 8, 0, 1],
150+
data: [
151+
{marker: {size: 7}},
152+
{marker: {size: 4}},
153+
{marker: {size: 5}},
154+
{marker: {size: 6}}
155+
]
156+
});
157+
});
158+
});
159+
160+
describe('computing trace layout', function() {
161+
var frames;
162+
163+
beforeEach(function(done) {
164+
frames = [{
165+
name: 'frame0',
166+
layout: {'margin.l': 40}
167+
}, {
168+
name: 'frame1',
169+
layout: {'margin.l': 80}
170+
}];
171+
172+
Plotly.addFrames(gd, frames).then(done);
173+
});
174+
175+
it('merges layouts', function() {
176+
frames[0].baseFrame = frames[1].name;
177+
var result = computeFrame(gd, 'frame0');
178+
179+
expect(result).toEqual({
180+
layout: {margin: {l: 40}}
181+
});
182+
});
183+
184+
});
185+
});

test/jasmine/tests/merge_frame_test.js

-48
This file was deleted.

0 commit comments

Comments
 (0)