Skip to content

Commit 9c4c4b3

Browse files
committed
Extend keyedContainer to handle nested keys
1 parent 6aefb41 commit 9c4c4b3

File tree

3 files changed

+95
-264
lines changed

3 files changed

+95
-264
lines changed

src/lib/keyed_container.js

+24-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var NONE = 0;
1717
var NAME = 1;
1818
var VALUE = 2;
1919
var BOTH = 3;
20+
var UNSET = 4;
2021

2122
module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
2223
keyName = keyName || 'name';
@@ -43,16 +44,18 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
4344
var obj = {
4445
// NB: this does not actually modify the baseObj
4546
set: function(name, value) {
46-
var changeType = NONE;
47+
var changeType = value === null ? UNSET : NONE;
48+
4749
var idx = indexLookup[name];
4850
if(idx === undefined) {
49-
changeType = BOTH;
51+
changeType = changeType | BOTH;
5052
idx = arr.length;
5153
indexLookup[name] = idx;
5254
} else if(value !== (isSimpleValueProp ? arr[idx][valueName] : nestedProperty(arr[idx], valueName).get())) {
53-
changeType = VALUE;
55+
changeType = changeType | VALUE;
5456
}
55-
var newValue = {};
57+
58+
var newValue = arr[idx] = arr[idx] || {};
5659
newValue[keyName] = name;
5760

5861
if(isSimpleValueProp) {
@@ -61,7 +64,11 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
6164
nestedProperty(newValue, valueName).set(value);
6265
}
6366

64-
arr[idx] = newValue;
67+
// If it's not an unset, force that bit to be unset. This is all related to the fact
68+
// that undefined and null are a bit specially implemented in nestedProperties.
69+
if(value !== null) {
70+
changeType = changeType & ~UNSET;
71+
}
6572

6673
changeTypes[idx] = changeTypes[idx] | changeType;
6774

@@ -93,7 +100,17 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
93100
},
94101
remove: function(name) {
95102
var idx = indexLookup[name];
103+
96104
if(idx === undefined) return obj;
105+
106+
var object = arr[idx];
107+
if(Object.keys(object).length > 2) {
108+
// This object contains more than just the key/value, so unset
109+
// the value without modifying the entry otherwise:
110+
changeTypes[idx] = changeTypes[idx] | VALUE;
111+
return obj.set(name, null);
112+
}
113+
97114
for(i = idx; i < arr.length; i++) {
98115
changeTypes[i] = changeTypes[i] | BOTH;
99116
}
@@ -118,9 +135,9 @@ module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
118135
}
119136
if(changeTypes[idx] & VALUE) {
120137
if(isSimpleValueProp) {
121-
update[astr + '.' + valueName] = arr[idx][valueName];
138+
update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : arr[idx][valueName];
122139
} else {
123-
update[astr + '.' + valueName] = nestedProperty(arr[idx], valueName).get();
140+
update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : nestedProperty(arr[idx], valueName).get();
124141
}
125142
}
126143
} else {

test/jasmine/tests/legend_test.js

+6-256
Original file line numberDiff line numberDiff line change
@@ -635,256 +635,6 @@ describe('legend restyle update', function() {
635635
describe('legend interaction', function() {
636636
'use strict';
637637

638-
describe('pie chart', function() {
639-
var mockCopy, gd, legendItems, legendItem, legendLabels, legendLabel;
640-
var testEntry = 2;
641-
642-
beforeAll(function(done) {
643-
var mock = require('@mocks/pie_simple.json');
644-
mockCopy = Lib.extendDeep({}, mock);
645-
gd = createGraphDiv();
646-
647-
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
648-
legendItems = d3.selectAll('rect.legendtoggle')[0];
649-
legendLabels = d3.selectAll('text.legendtext')[0];
650-
legendItem = legendItems[testEntry];
651-
legendLabel = legendLabels[testEntry].innerHTML;
652-
done();
653-
});
654-
});
655-
656-
afterAll(destroyGraphDiv);
657-
658-
describe('single click', function() {
659-
it('should hide slice', function(done) {
660-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
661-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
662-
setTimeout(function() {
663-
expect(gd._fullLayout.hiddenlabels.length).toBe(1);
664-
expect(gd._fullLayout.hiddenlabels[0]).toBe(legendLabel);
665-
done();
666-
}, DBLCLICKDELAY + 20);
667-
});
668-
669-
it('should fade legend item', function() {
670-
expect(+legendItem.parentNode.style.opacity).toBeLessThan(1);
671-
});
672-
673-
it('should unhide slice', function(done) {
674-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
675-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
676-
setTimeout(function() {
677-
expect(gd._fullLayout.hiddenlabels.length).toBe(0);
678-
done();
679-
}, DBLCLICKDELAY + 20);
680-
});
681-
682-
it('should unfade legend item', function() {
683-
expect(+legendItem.parentNode.style.opacity).toBe(1);
684-
});
685-
});
686-
687-
describe('double click', function() {
688-
it('should hide other slices', function(done) {
689-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
690-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
691-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
692-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
693-
setTimeout(function() {
694-
expect(gd._fullLayout.hiddenlabels.length).toBe((legendItems.length - 1));
695-
expect(gd._fullLayout.hiddenlabels.indexOf(legendLabel)).toBe(-1);
696-
done();
697-
}, 20);
698-
});
699-
700-
it('should fade other legend items', function() {
701-
var legendItemi;
702-
for(var i = 0; i < legendItems.length; i++) {
703-
legendItemi = legendItems[i];
704-
if(i === testEntry) {
705-
expect(+legendItemi.parentNode.style.opacity).toBe(1);
706-
} else {
707-
expect(+legendItemi.parentNode.style.opacity).toBeLessThan(1);
708-
}
709-
}
710-
});
711-
712-
it('should unhide all slices', function(done) {
713-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
714-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
715-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
716-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
717-
setTimeout(function() {
718-
expect(gd._fullLayout.hiddenlabels.length).toBe(0);
719-
done();
720-
}, 20);
721-
});
722-
723-
it('should unfade legend items', function() {
724-
var legendItemi;
725-
for(var i = 0; i < legendItems.length; i++) {
726-
legendItemi = legendItems[i];
727-
expect(+legendItemi.parentNode.style.opacity).toBe(1);
728-
}
729-
});
730-
});
731-
});
732-
733-
describe('non-pie chart', function() {
734-
var mockCopy, gd, legendItems, legendItem;
735-
var testEntry = 2;
736-
737-
beforeAll(function(done) {
738-
var mock = require('@mocks/29.json');
739-
mockCopy = Lib.extendDeep({}, mock);
740-
gd = createGraphDiv();
741-
742-
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
743-
legendItems = d3.selectAll('rect.legendtoggle')[0];
744-
legendItem = legendItems[testEntry];
745-
done();
746-
});
747-
});
748-
749-
afterAll(destroyGraphDiv);
750-
751-
describe('single click', function() {
752-
it('should hide series', function(done) {
753-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
754-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
755-
setTimeout(function() {
756-
expect(gd.data[2].visible).toBe('legendonly');
757-
done();
758-
}, DBLCLICKDELAY + 20);
759-
});
760-
761-
it('should fade legend item', function() {
762-
expect(+legendItem.parentNode.style.opacity).toBeLessThan(1);
763-
});
764-
765-
it('should unhide series', function(done) {
766-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
767-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
768-
setTimeout(function() {
769-
expect(gd.data[2].visible).toBe(true);
770-
done();
771-
}, DBLCLICKDELAY + 20);
772-
});
773-
774-
it('should unfade legend item', function() {
775-
expect(+legendItem.parentNode.style.opacity).toBe(1);
776-
});
777-
});
778-
779-
describe('double click', function() {
780-
it('should hide series', function(done) {
781-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
782-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
783-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
784-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
785-
setTimeout(function() {
786-
for(var i = 0; i < legendItems.length; i++) {
787-
if(i === testEntry) {
788-
expect(gd.data[i].visible).toBe(true);
789-
} else {
790-
expect(gd.data[i].visible).toBe('legendonly');
791-
}
792-
}
793-
done();
794-
}, 20);
795-
});
796-
797-
it('should fade legend item', function() {
798-
var legendItemi;
799-
for(var i = 0; i < legendItems.length; i++) {
800-
legendItemi = legendItems[i];
801-
if(i === testEntry) {
802-
expect(+legendItemi.parentNode.style.opacity).toBe(1);
803-
} else {
804-
expect(+legendItemi.parentNode.style.opacity).toBeLessThan(1);
805-
}
806-
}
807-
});
808-
809-
it('should unhide series', function(done) {
810-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
811-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
812-
legendItem.dispatchEvent(new MouseEvent('mousedown'));
813-
legendItem.dispatchEvent(new MouseEvent('mouseup'));
814-
setTimeout(function() {
815-
for(var i = 0; i < legendItems.length; i++) {
816-
expect(gd.data[i].visible).toBe(true);
817-
}
818-
done();
819-
}, 20);
820-
});
821-
822-
it('should unfade legend items', function() {
823-
var legendItemi;
824-
for(var i = 0; i < legendItems.length; i++) {
825-
legendItemi = legendItems[i];
826-
expect(+legendItemi.parentNode.style.opacity).toBe(1);
827-
}
828-
});
829-
});
830-
});
831-
832-
describe('carpet plots', function() {
833-
afterAll(destroyGraphDiv);
834-
835-
function _click(index) {
836-
var item = d3.selectAll('rect.legendtoggle')[0][index || 0];
837-
return new Promise(function(resolve) {
838-
item.dispatchEvent(new MouseEvent('mousedown'));
839-
item.dispatchEvent(new MouseEvent('mouseup'));
840-
setTimeout(resolve, DBLCLICKDELAY + 20);
841-
});
842-
}
843-
844-
function _dblclick(index) {
845-
var item = d3.selectAll('rect.legendtoggle')[0][index || 0];
846-
return new Promise(function(resolve) {
847-
item.dispatchEvent(new MouseEvent('mousedown'));
848-
item.dispatchEvent(new MouseEvent('mouseup'));
849-
item.dispatchEvent(new MouseEvent('mousedown'));
850-
item.dispatchEvent(new MouseEvent('mouseup'));
851-
setTimeout(resolve, 20);
852-
});
853-
}
854-
855-
function assertVisible(gd, expectation) {
856-
var actual = gd._fullData.map(function(trace) { return trace.visible; });
857-
expect(actual).toEqual(expectation);
858-
}
859-
860-
it('should ignore carpet traces when toggling', function(done) {
861-
var _mock = Lib.extendDeep({}, require('@mocks/cheater.json'));
862-
var gd = createGraphDiv();
863-
864-
Plotly.plot(gd, _mock).then(function() {
865-
assertVisible(gd, [true, true, true, true]);
866-
})
867-
.then(_click)
868-
.then(function() {
869-
assertVisible(gd, [true, 'legendonly', true, true]);
870-
})
871-
.then(_click)
872-
.then(function() {
873-
assertVisible(gd, [true, true, true, true]);
874-
})
875-
.then(_dblclick)
876-
.then(function() {
877-
assertVisible(gd, [true, true, 'legendonly', 'legendonly']);
878-
})
879-
.then(_dblclick)
880-
.then(function() {
881-
assertVisible(gd, [true, true, true, true]);
882-
})
883-
.catch(fail)
884-
.then(done);
885-
});
886-
});
887-
888638
describe('editable mode interactions', function() {
889639
var gd;
890640
var mock = {
@@ -927,15 +677,15 @@ describe('legend interaction', function() {
927677
// Set the name of the third legend item:
928678
return _setValue(3, 'bar');
929679
}).then(function() {
930-
expect(gd.data[1].transforms[0].groupnames).toEqual([
931-
{name: 'bar', group: 3}
680+
expect(gd.data[1].transforms[0].styles).toEqual([
681+
{value: {name: 'bar'}, target: 3}
932682
]);
933683
}).then(function() {
934684
return _setValue(4, 'asdf');
935685
}).then(function() {
936-
expect(gd.data[1].transforms[0].groupnames).toEqual([
937-
{name: 'bar', group: 3},
938-
{name: 'asdf', group: 4}
686+
expect(gd.data[1].transforms[0].styles).toEqual([
687+
{value: {name: 'bar'}, target: 3},
688+
{value: {name: 'asdf'}, target: 4}
939689
]);
940690
}).then(function() {
941691
// Unset the group names:
@@ -944,7 +694,7 @@ describe('legend interaction', function() {
944694
return _setValue(4, '');
945695
}).then(function() {
946696
// Verify the group names have been cleaned up:
947-
expect(gd.data[1].transforms[0].groupnames).toEqual([]);
697+
expect(gd.data[1].transforms[0].styles).toEqual([]);
948698
}).catch(fail).then(done);
949699
});
950700
});

0 commit comments

Comments
 (0)