Skip to content

Commit b612ac3

Browse files
committed
Methods for extendTrace and extendLayout
1 parent a0da51d commit b612ac3

File tree

4 files changed

+141
-29
lines changed

4 files changed

+141
-29
lines changed

src/lib/index.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -594,13 +594,15 @@ lib.objectFromPath = function(path, value) {
594594
* // returns { nested: { test: {path: 'value'}}}
595595
*/
596596

597-
// Store this to avoid recompiling regex on every prop since this may happen many
598-
// many times for animations.
599-
// TODO: Premature optimization? Remove?
600-
var dottedPropertyRegex = /^([^\.]*)\../;
597+
// Store this to avoid recompiling regex on *every* prop since this may happen many
598+
// many times for animations. Could maybe be inside the function. Not sure about
599+
// scoping vs. recompilation tradeoff, but at least it's not just inlining it into
600+
// the inner loop.
601+
var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/;
602+
var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/;
601603

602604
lib.expandObjectPaths = function(data) {
603-
var match, key, prop, datum;
605+
var match, key, prop, datum, idx, dest, trailingPath;
604606
if(typeof data === 'object' && !Array.isArray(data)) {
605607
for(key in data) {
606608
if(data.hasOwnProperty(key)) {
@@ -611,12 +613,30 @@ lib.expandObjectPaths = function(data) {
611613
delete data[key];
612614

613615
data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
616+
} else if((match = key.match(indexedPropertyRegex))) {
617+
datum = data[key];
618+
619+
prop = match[1];
620+
idx = parseInt(match[2]);
621+
622+
delete data[key];
623+
624+
data[prop] = data[prop] || [];
625+
626+
if(match[3] === '.') {
627+
trailingPath = match[4];
628+
dest = data[prop][idx] = data[prop][idx] || {};
629+
lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum)));
630+
} else {
631+
data[prop][idx] = lib.expandObjectPaths(datum);
632+
}
614633
} else {
615634
data[key] = lib.expandObjectPaths(data[key]);
616635
}
617636
}
618637
}
619638
}
639+
620640
return data;
621641
};
622642

src/plots/plots.js

+66-21
Original file line numberDiff line numberDiff line change
@@ -1299,7 +1299,7 @@ plots.modifyFrames = function(gd, operations) {
12991299
*/
13001300
plots.computeFrame = function(gd, frameName) {
13011301
var frameLookup = gd._transitionData._frameHash;
1302-
var i, traceIndices, traceIndex, expandedObj, destIndex, copy;
1302+
var i, traceIndices, traceIndex, destIndex;
13031303

13041304
var framePtr = frameLookup[frameName];
13051305

@@ -1361,49 +1361,94 @@ plots.computeFrame = function(gd, frameName) {
13611361
result.traces[destIndex] = traceIndex;
13621362
}
13631363

1364-
try {
1365-
result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]);
1366-
} catch (e) {
1367-
console.error(e);
1368-
}
1364+
result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]);
13691365
}
13701366
}
13711367
}
13721368

13731369
return result;
13741370
};
13751371

1376-
plots.extendObjectWithContainers = function (dest, src, containerPaths) {
1372+
/**
1373+
* Extend an object, treating container arrays very differently by extracting
1374+
* their contents and merging them separately.
1375+
*
1376+
* This exists so that we can extendDeepNoArrays and avoid stepping into data
1377+
* arrays without knowledge of the plot schema, but so that we may also manually
1378+
* recurse into known container arrays, such as transforms.
1379+
*
1380+
* See extendTrace and extendLayout below for usage.
1381+
*/
1382+
plots.extendObjectWithContainers = function(dest, src, containerPaths) {
1383+
var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer;
13771384
var copy = Lib.extendDeepNoArrays({}, src || {});
13781385
var expandedObj = Lib.expandObjectPaths(copy);
1386+
var containerObj = {};
1387+
1388+
// Step through and extract any container properties. Otherwise extendDeepNoArrays
1389+
// will clobber any existing properties with an empty array and then supplyDefaults
1390+
// will reset everything to defaults.
1391+
if(containerPaths && containerPaths.length) {
1392+
for(i = 0; i < containerPaths.length; i++) {
1393+
containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]);
1394+
containerVal = containerProp.get();
1395+
containerProp.set(null);
1396+
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);
1397+
}
1398+
}
13791399

1380-
if (containerPaths && containerPaths.length) {
1381-
for (var i = 0; i < containerPaths.length; i++) {
1382-
var srcProp = Lib.nestedProperty(src, containerPaths[i]);
1383-
var srcVal = srcProp.get();
1384-
1385-
console.log('src, containerPaths[i], srcVal:', src, containerPaths[i], srcVal);
1386-
1400+
dest = Lib.extendDeepNoArrays(dest || {}, expandedObj);
13871401

1388-
if (!srcVal) continue;
1402+
if(containerPaths && containerPaths.length) {
1403+
for(i = 0; i < containerPaths.length; i++) {
1404+
srcProp = Lib.nestedProperty(containerObj, containerPaths[i]);
1405+
srcContainer = srcProp.get();
13891406

1390-
var destProp = Lib.nestedProperty(src, containerPaths[i]);
1391-
var destVal = destProp.get();
1407+
if(!srcContainer) continue;
13921408

1409+
destProp = Lib.nestedProperty(dest, containerPaths[i]);
13931410

1411+
destContainer = destProp.get();
1412+
if(!Array.isArray(destContainer)) {
1413+
destContainer = [];
1414+
destProp.set(destContainer);
1415+
}
13941416

1417+
for(j = 0; j < srcContainer.length; j++) {
1418+
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]);
1419+
}
13951420
}
13961421
}
13971422

1398-
return Lib.extendDeepNoArrays(dest || {}, expandedObj);
1423+
return dest;
13991424
};
14001425

1401-
plots.extendTrace = function (destTrace, srcTrace) {
1426+
/*
1427+
* Extend a trace definition. This method:
1428+
*
1429+
* 1. directly transfers any array references
1430+
* 2. manually recurses into container arrays like transforms
1431+
*
1432+
* The result is the original object reference with the new contents merged in.
1433+
*/
1434+
plots.extendTrace = function(destTrace, srcTrace) {
14021435
return plots.extendObjectWithContainers(destTrace, srcTrace, ['transforms']);
14031436
};
14041437

1405-
plots.extendLayout = function (destLayout, srcLayout) {
1406-
return plots.extendObjectWithContainers(destLayout, srcLayout, []);
1438+
/*
1439+
* Extend a layout definition. This method:
1440+
*
1441+
* 1. directly transfers any array references (not critically important for
1442+
* layout since there aren't really data arrays)
1443+
* 2. manually recurses into container arrays like annotations
1444+
*
1445+
* The result is the original object reference with the new contents merged in.
1446+
*/
1447+
plots.extendLayout = function(destLayout, srcLayout) {
1448+
return plots.extendObjectWithContainers(destLayout, srcLayout, [
1449+
'annotations',
1450+
'shapes'
1451+
]);
14071452
};
14081453

14091454
/**

test/jasmine/tests/lib_test.js

+48
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,54 @@ describe('Test lib.js:', function() {
537537
expect(input).toBe(expanded);
538538
expect(origArray).toBe(newArray);
539539
});
540+
541+
it('expands bracketed array notation', function() {
542+
var input = {'marker[1]': {color: 'red'}};
543+
var expected = {marker: [undefined, {color: 'red'}]};
544+
expect(Lib.expandObjectPaths(input)).toEqual(expected);
545+
});
546+
547+
it('expands nested arrays', function() {
548+
var input = {'marker[1].range[1]': 5};
549+
var expected = {marker: [undefined, {range: [undefined, 5]}]};
550+
var computed = Lib.expandObjectPaths(input);
551+
expect(computed).toEqual(expected);
552+
});
553+
554+
it('expands bracketed array with more nested attributes', function() {
555+
var input = {'marker[1]': {'color.alpha': 2}};
556+
var expected = {marker: [undefined, {color: {alpha: 2}}]};
557+
var computed = Lib.expandObjectPaths(input);
558+
expect(computed).toEqual(expected);
559+
});
560+
561+
it('expands bracketed array notation without further nesting', function() {
562+
var input = {'marker[1]': 8};
563+
var expected = {marker: [undefined, 8]};
564+
var computed = Lib.expandObjectPaths(input);
565+
expect(computed).toEqual(expected);
566+
});
567+
568+
it('expands bracketed array notation with further nesting', function() {
569+
var input = {'marker[1].size': 8};
570+
var expected = {marker: [undefined, {size: 8}]};
571+
var computed = Lib.expandObjectPaths(input);
572+
expect(computed).toEqual(expected);
573+
});
574+
575+
it('expands bracketed array notation with further nesting', function() {
576+
var input = {'marker[1].size.magnitude': 8};
577+
var expected = {marker: [undefined, {size: {magnitude: 8}}]};
578+
var computed = Lib.expandObjectPaths(input);
579+
expect(computed).toEqual(expected);
580+
});
581+
582+
it('combines changes', function() {
583+
var input = {'marker[1].range[1]': 5, 'marker[1].range[0]': 4};
584+
var expected = {marker: [undefined, {range: [4, 5]}]};
585+
var computed = Lib.expandObjectPaths(input);
586+
expect(computed).toEqual(expected);
587+
});
540588
});
541589

542590
describe('coerce', function() {

test/jasmine/tests/transition_test.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function runTests(transitionDuration) {
2828
destroyGraphDiv();
2929
});
3030

31-
/*it('resolves only once the transition has completed', function(done) {
31+
it('resolves only once the transition has completed', function(done) {
3232
var t1 = Date.now();
3333
var traces = plotApiHelpers.coerceTraceIndices(gd, null);
3434

@@ -63,7 +63,7 @@ function runTests(transitionDuration) {
6363
.then(function() {
6464
expect(trEndCnt).toEqual(1);
6565
}).catch(fail).then(done);
66-
});*/
66+
});
6767

6868
it('transitions a transform', function(done) {
6969
Plotly.restyle(gd, {
@@ -99,7 +99,6 @@ function runTests(transitionDuration) {
9999
}]);
100100
}).catch(fail).then(done);
101101
});
102-
return;
103102

104103
// This doesn't really test anything that the above tests don't cover, but it combines
105104
// the behavior and attempts to ensure chaining and events happen in the correct order.

0 commit comments

Comments
 (0)