Skip to content

Commit e8fc236

Browse files
committed
Merge pull request #378 from n-riesco/issue-148-and-359
Shape clean up
2 parents 537d87e + 92e46e3 commit e8fc236

File tree

2 files changed

+149
-71
lines changed

2 files changed

+149
-71
lines changed

src/components/shapes/index.js

+79-57
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ function linearToData(ax) { return ax.type === 'category' ? ax.l2c : ax.l2d; }
8989

9090
shapes.drawAll = function(gd) {
9191
var fullLayout = gd._fullLayout;
92-
fullLayout._shapelayer.selectAll('path').remove();
9392
for(var i = 0; i < fullLayout.shapes.length; i++) {
9493
shapes.draw(gd, i);
9594
}
@@ -115,86 +114,106 @@ shapes.add = function(gd) {
115114
// if opt is blank, val can be 'add' or a full options object to add a new
116115
// annotation at that point in the array, or 'remove' to delete this one
117116
shapes.draw = function(gd, index, opt, value) {
118-
var layout = gd.layout,
119-
fullLayout = gd._fullLayout,
120-
i;
121-
122-
// TODO: abstract out these drawAll, add, and remove blocks for shapes and annotations
123117
if(!isNumeric(index) || index===-1) {
124118
// no index provided - we're operating on ALL shapes
125119
if(!index && Array.isArray(value)) {
126-
// a whole annotation array is passed in
127-
// (as in, redo of delete all)
128-
layout.shapes = value;
129-
shapes.supplyLayoutDefaults(layout, fullLayout);
130-
shapes.drawAll(gd);
120+
replaceAllShapes(gd, value);
131121
return;
132122
}
133123
else if(value==='remove') {
134-
// delete all
135-
delete layout.shapes;
136-
fullLayout.shapes = [];
137-
shapes.drawAll(gd);
124+
deleteAllShapes(gd);
138125
return;
139126
}
140127
else if(opt && value!=='add') {
141-
// make the same change to all shapes
142-
for(i = 0; i < fullLayout.shapes.length; i++) {
143-
shapes.draw(gd, i, opt, value);
144-
}
128+
updateAllShapes(gd, opt, value);
145129
return;
146130
}
147131
else {
148132
// add a new empty annotation
149-
index = fullLayout.shapes.length;
150-
fullLayout.shapes.push({});
133+
index = gd._fullLayout.shapes.length;
134+
gd._fullLayout.shapes.push({});
151135
}
152136
}
153137

154138
if(!opt && value) {
155139
if(value==='remove') {
156-
fullLayout._shapelayer.selectAll('[data-index="'+index+'"]')
157-
.remove();
158-
fullLayout.shapes.splice(index,1);
159-
layout.shapes.splice(index,1);
160-
for(i=index; i<fullLayout.shapes.length; i++) {
161-
fullLayout._shapelayer
162-
.selectAll('[data-index="'+(i+1)+'"]')
163-
.attr('data-index',String(i));
164-
165-
// redraw all shapes past the removed one,
166-
// so they bind to the right events
167-
shapes.draw(gd,i);
168-
}
140+
deleteShape(gd, index);
169141
return;
170142
}
171143
else if(value==='add' || Plotly.Lib.isPlainObject(value)) {
172-
fullLayout.shapes.splice(index,0,{});
144+
insertShape(gd, index, value);
145+
}
146+
}
173147

174-
var rule = Plotly.Lib.isPlainObject(value) ?
175-
Plotly.Lib.extendFlat({}, value) :
176-
{text: 'New text'};
148+
updateShape(gd, index, opt, value);
149+
};
177150

178-
if(layout.shapes) {
179-
layout.shapes.splice(index, 0, rule);
180-
} else {
181-
layout.shapes = [rule];
182-
}
151+
function replaceAllShapes(gd, newShapes) {
152+
gd.layout.shapes = newShapes;
153+
shapes.supplyLayoutDefaults(gd.layout, gd._fullLayout);
154+
shapes.drawAll(gd);
155+
}
183156

184-
for(i=fullLayout.shapes.length-1; i>index; i--) {
185-
fullLayout._shapelayer
186-
.selectAll('[data-index="'+(i-1)+'"]')
187-
.attr('data-index',String(i));
188-
shapes.draw(gd,i);
189-
}
190-
}
157+
function deleteAllShapes(gd) {
158+
delete gd.layout.shapes;
159+
gd._fullLayout.shapes = [];
160+
shapes.drawAll(gd);
161+
}
162+
163+
function updateAllShapes(gd, opt, value) {
164+
for(var i = 0; i < gd._fullLayout.shapes.length; i++) {
165+
shapes.draw(gd, i, opt, value);
166+
}
167+
}
168+
169+
function deleteShape(gd, index) {
170+
gd._fullLayout._shapelayer.selectAll('[data-index="' + index + '"]')
171+
.remove();
172+
173+
gd._fullLayout.shapes.splice(index, 1);
174+
175+
gd.layout.shapes.splice(index, 1);
176+
177+
for(var i = index; i < gd._fullLayout.shapes.length; i++) {
178+
// redraw all shapes past the removed one,
179+
// so they bind to the right events
180+
gd._fullLayout._shapelayer
181+
.selectAll('[data-index="' + (i+1) + '"]')
182+
.attr('data-index', String(i));
183+
shapes.draw(gd, i);
184+
}
185+
}
186+
187+
function insertShape(gd, index, newShape) {
188+
gd._fullLayout.shapes.splice(index, 0, {});
189+
190+
var rule = Plotly.Lib.isPlainObject(newShape) ?
191+
Plotly.Lib.extendFlat({}, newShape) :
192+
{text: 'New text'};
193+
194+
if(gd.layout.shapes) {
195+
gd.layout.shapes.splice(index, 0, rule);
196+
} else {
197+
gd.layout.shapes = [rule];
191198
}
192199

200+
for(var i = gd._fullLayout.shapes.length - 1; i > index; i--) {
201+
gd._fullLayout._shapelayer
202+
.selectAll('[data-index="' + (i - 1) + '"]')
203+
.attr('data-index', String(i));
204+
shapes.draw(gd, i);
205+
}
206+
}
207+
208+
function updateShape(gd, index, opt, value) {
209+
var i;
210+
193211
// remove the existing shape if there is one
194-
fullLayout._shapelayer.selectAll('[data-index="'+index+'"]').remove();
212+
gd._fullLayout._shapelayer.selectAll('[data-index="' + index + '"]')
213+
.remove();
195214

196215
// remember a few things about what was already there,
197-
var optionsIn = layout.shapes[index];
216+
var optionsIn = gd.layout.shapes[index];
198217

199218
// (from annos...) not sure how we're getting here... but C12 is seeing a bug
200219
// where we fail here when they add/remove annotations
@@ -261,8 +280,8 @@ shapes.draw = function(gd, index, opt, value) {
261280
optionsIn[posAttr] = position;
262281
}
263282

264-
var options = handleShapeDefaults(optionsIn, fullLayout);
265-
fullLayout.shapes[index] = options;
283+
var options = handleShapeDefaults(optionsIn, gd._fullLayout);
284+
gd._fullLayout.shapes[index] = options;
266285

267286
var attrs = {
268287
'data-index': String(index),
@@ -273,15 +292,18 @@ shapes.draw = function(gd, index, opt, value) {
273292

274293
var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)';
275294

276-
var path = fullLayout._shapelayer.append('path')
295+
var path = gd._fullLayout._shapelayer.append('path')
277296
.attr(attrs)
278297
.style('opacity', options.opacity)
279298
.call(Plotly.Color.stroke, lineColor)
280299
.call(Plotly.Color.fill, options.fillcolor)
281300
.call(Plotly.Drawing.dashLine, options.line.dash, options.line.width);
282301

283-
if(clipAxes) path.call(Plotly.Drawing.setClipUrl, 'clip' + fullLayout._uid + clipAxes);
284-
};
302+
if(clipAxes) {
303+
path.call(Plotly.Drawing.setClipUrl,
304+
'clip' + gd._fullLayout._uid + clipAxes);
305+
}
306+
}
285307

286308
function decodeDate(convertToPx) {
287309
return function(v) { return convertToPx(v.replace('_', ' ')); };

test/jasmine/tests/shapes_test.js

+70-14
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var createGraphDiv = require('../assets/create_graph_div');
77
var destroyGraphDiv = require('../assets/destroy_graph_div');
88

99

10-
describe('Test shapes nodes', function() {
10+
describe('Test shapes:', function() {
1111
'use strict';
1212

1313
var mock = require('@mocks/shapes.json');
@@ -28,26 +28,82 @@ describe('Test shapes nodes', function() {
2828
return d3.selectAll('.shapelayer').size();
2929
}
3030

31-
function countPaths() {
31+
function countShapePaths() {
3232
return d3.selectAll('.shapelayer > path').size();
3333
}
3434

35-
it('has one *shapelayer* node', function() {
36-
expect(countShapeLayers()).toEqual(1);
37-
});
35+
describe('DOM', function() {
36+
it('has one *shapelayer* node', function() {
37+
expect(countShapeLayers()).toEqual(1);
38+
});
39+
40+
it('has as many *path* nodes as there are shapes', function() {
41+
expect(countShapePaths()).toEqual(mock.layout.shapes.length);
42+
});
3843

39-
it('has as many *path* nodes as there are shapes', function() {
40-
expect(countPaths()).toEqual(mock.layout.shapes.length);
44+
it('should be able to get relayout', function(done) {
45+
expect(countShapeLayers()).toEqual(1);
46+
expect(countShapePaths()).toEqual(mock.layout.shapes.length);
47+
48+
Plotly.relayout(gd, {height: 200, width: 400}).then(function() {
49+
expect(countShapeLayers()).toEqual(1);
50+
expect(countShapePaths()).toEqual(mock.layout.shapes.length);
51+
}).then(done);
52+
});
4153
});
4254

43-
it('should be able to get relayout', function(done) {
44-
expect(countShapeLayers()).toEqual(1);
45-
expect(countPaths()).toEqual(mock.layout.shapes.length);
55+
function countShapes(gd) {
56+
return gd.layout.shapes ?
57+
gd.layout.shapes.length :
58+
0;
59+
}
60+
61+
function getLastShape(gd) {
62+
return gd.layout.shapes ?
63+
gd.layout.shapes[gd.layout.shapes.length - 1] :
64+
null;
65+
}
4666

47-
Plotly.relayout(gd, {height: 200, width: 400}).then(function() {
48-
expect(countShapeLayers()).toEqual(1);
49-
expect(countPaths()).toEqual(mock.layout.shapes.length);
50-
done();
67+
function getRandomShape() {
68+
return {
69+
x0: Math.random(),
70+
y0: Math.random(),
71+
x1: Math.random(),
72+
y1: Math.random()
73+
};
74+
}
75+
76+
describe('Plotly.relayout', function() {
77+
it('should be able to add a shape', function(done) {
78+
var pathCount = countShapePaths();
79+
var index = countShapes(gd);
80+
var shape = getRandomShape();
81+
82+
Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() {
83+
expect(countShapeLayers()).toEqual(1);
84+
expect(countShapePaths()).toEqual(pathCount + 1);
85+
expect(getLastShape(gd)).toEqual(shape);
86+
expect(countShapes(gd)).toEqual(index + 1);
87+
}).then(done);
88+
});
89+
90+
it('should be able to remove a shape', function(done) {
91+
var pathCount = countShapePaths();
92+
var index = countShapes(gd);
93+
var shape = getRandomShape();
94+
95+
Plotly.relayout(gd, 'shapes[' + index + ']', shape).then(function() {
96+
expect(countShapeLayers()).toEqual(1);
97+
expect(countShapePaths()).toEqual(pathCount + 1);
98+
expect(getLastShape(gd)).toEqual(shape);
99+
expect(countShapes(gd)).toEqual(index + 1);
100+
}).then(function() {
101+
Plotly.relayout(gd, 'shapes[' + index + ']', 'remove');
102+
}).then(function() {
103+
expect(countShapeLayers()).toEqual(1);
104+
expect(countShapePaths()).toEqual(pathCount);
105+
expect(countShapes(gd)).toEqual(index);
106+
}).then(done);
51107
});
52108
});
53109
});

0 commit comments

Comments
 (0)