Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0b4214f

Browse files
committed
Merge branch 'master' into gl3d-annotations
2 parents ef985cf + 200a677 commit 0b4214f

File tree

6 files changed

+156
-58
lines changed

6 files changed

+156
-58
lines changed

src/components/drawing/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,16 @@ drawing.dashStyle = function(dash, lineWidth) {
156156
return dash;
157157
};
158158

159+
// Same as fillGroupStyle, except in this case the selection may be a transition
160+
drawing.singleFillStyle = function(sel) {
161+
var node = d3.select(sel.node());
162+
var data = node.data();
163+
var fillcolor = (((data[0] || [])[0] || {}).trace || {}).fillcolor;
164+
if(fillcolor) {
165+
sel.call(Color.fill, fillcolor);
166+
}
167+
};
168+
159169
drawing.fillGroupStyle = function(s) {
160170
s.style('stroke-width', 0)
161171
.each(function(d) {

src/lib/svg_text_utils.js

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,36 @@ var SPLIT_TAGS = /(<[^<>]*>)/;
282282

283283
var ONE_TAG = /<(\/?)([^ >]*)(\s+(.*))?>/i;
284284

285-
// Style and href: pull them out of either single or double quotes.
286-
// Because we hack in other attributes with style (sub & sup), drop any trailing
287-
// semicolon in user-supplied styles so we can consistently append the tag-dependent style
285+
/*
286+
* style and href: pull them out of either single or double quotes. Also
287+
* - target: (_blank|_self|_parent|_top|framename)
288+
* note that you can't use target to get a popup but if you use popup,
289+
* a `framename` will be passed along as the name of the popup window.
290+
* per the spec, cannot contain whitespace.
291+
* for backward compatibility we default to '_blank'
292+
* - popup: a custom one for us to enable popup (new window) links. String
293+
* for window.open -> strWindowFeatures, like 'menubar=yes,width=500,height=550'
294+
* note that at least in Chrome, you need to give at least one property
295+
* in this string or the page will open in a new tab anyway. We follow this
296+
* convention and will not make a popup if this string is empty.
297+
* per the spec, cannot contain whitespace.
298+
*
299+
* Because we hack in other attributes with style (sub & sup), drop any trailing
300+
* semicolon in user-supplied styles so we can consistently append the tag-dependent style
301+
*/
288302
var STYLEMATCH = /(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i;
289303
var HREFMATCH = /(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i;
304+
var TARGETMATCH = /(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i;
305+
var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i;
306+
307+
// dedicated matcher for these quoted regexes, that can return their results
308+
// in two different places
309+
function getQuotedMatch(_str, re) {
310+
if(!_str) return null;
311+
var match = _str.match(re);
312+
return match && (match[3] || match[4]);
313+
}
314+
290315

291316
var COLORMATCH = /(^|;)\s*color:/;
292317

@@ -297,14 +322,14 @@ exports.plainText = function(_str) {
297322
};
298323

299324
function replaceFromMapObject(_str, list) {
300-
var out = _str || '';
325+
if(!_str) return '';
301326

302327
for(var i = 0; i < list.length; i++) {
303328
var item = list[i];
304-
out = out.replace(item.regExp, item.sub);
329+
_str = _str.replace(item.regExp, item.sub);
305330
}
306331

307-
return out;
332+
return _str;
308333
}
309334

310335
function convertEntities(_str) {
@@ -354,8 +379,7 @@ function convertToSVG(_str) {
354379

355380
// anchor is the only tag that doesn't turn into a tspan
356381
if(tag === 'a') {
357-
var hrefMatch = extra && extra.match(HREFMATCH);
358-
var href = hrefMatch && (hrefMatch[3] || hrefMatch[4]);
382+
var href = getQuotedMatch(extra, HREFMATCH);
359383

360384
out = '<a';
361385

@@ -364,7 +388,34 @@ function convertToSVG(_str) {
364388
var dummyAnchor = document.createElement('a');
365389
dummyAnchor.href = href;
366390
if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) {
367-
out += ' xlink:show="new" xlink:href="' + encodeForHTML(href) + '"';
391+
href = encodeForHTML(href);
392+
393+
// look for target and popup specs
394+
var target = encodeForHTML(getQuotedMatch(extra, TARGETMATCH)) || '_blank';
395+
var popup = encodeForHTML(getQuotedMatch(extra, POPUPMATCH));
396+
397+
/*
398+
* xlink:show is for backward compatibility only,
399+
* newer browsers allow target just like html links.
400+
*/
401+
var xlinkShow = (target === '_blank' || target.charAt(0) !== '_') ?
402+
'new' : 'replace';
403+
404+
out += ' xlink:show="' + xlinkShow + '" target="' + target +
405+
'" xlink:href="' + href + '"';
406+
407+
if(popup) {
408+
/*
409+
* Add the window.open call to create a popup
410+
* Even when popup is specified, we leave the original
411+
* href and target in place in case javascript is
412+
* unavailable in this context (like downloaded svg)
413+
* and for accessibility and so users can see where
414+
* the link will lead.
415+
*/
416+
out += ' onclick="window.open(\'' + href + '\',\'' + target +
417+
'\',\'' + popup + '\');return false;"';
418+
}
368419
}
369420
}
370421
}
@@ -380,8 +431,7 @@ function convertToSVG(_str) {
380431
// now add style, from both the tag name and any extra css
381432
// Most of the svg css that users will care about is just like html,
382433
// but font color is different (uses fill). Let our users ignore this.
383-
var cssMatch = extra && extra.match(STYLEMATCH);
384-
var css = cssMatch && (cssMatch[3] || cssMatch[4]);
434+
var css = getQuotedMatch(extra, STYLEMATCH);
385435
if(css) {
386436
css = encodeForHTML(css.replace(COLORMATCH, '$1 fill:'));
387437
if(tagStyle) css += ';' + tagStyle;

src/traces/scatter/plot.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,12 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
340340
// For the sake of animations, wrap the points around so that
341341
// the points on the axes are the first two points. Otherwise
342342
// animations get a little crazy if the number of points changes.
343-
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1));
343+
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1))
344+
.call(Drawing.singleFillStyle);
344345
} else {
345346
// fill to self: just join the path to itself
346-
transition(ownFillEl3).attr('d', fullpath + 'Z');
347+
transition(ownFillEl3).attr('d', fullpath + 'Z')
348+
.call(Drawing.singleFillStyle);
347349
}
348350
}
349351
}
@@ -354,15 +356,17 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
354356
// contours, we just add the two paths closed on themselves.
355357
// This makes strange results if one path is *not* entirely
356358
// inside the other, but then that is a strange usage.
357-
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z');
359+
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z')
360+
.call(Drawing.singleFillStyle);
358361
}
359362
else {
360363
// tonextx/y: for now just connect endpoints with lines. This is
361364
// the correct behavior if the endpoints are at the same value of
362365
// y/x, but if they *aren't*, we should ideally do more complicated
363366
// things depending on whether the new endpoint projects onto the
364367
// existing curve or off the end of it
365-
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z');
368+
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z')
369+
.call(Drawing.singleFillStyle);
366370
}
367371
trace._polygons = trace._polygons.concat(prevPolygons);
368372
}

test/jasmine/tests/scatter_test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,27 @@ describe('end-to-end scatter tests', function() {
575575
.catch(fail)
576576
.then(done);
577577
});
578+
579+
it('animates fillcolor', function(done) {
580+
function fill() {
581+
return d3.selectAll('.js-fill').node().style.fill;
582+
}
583+
584+
Plotly.plot(gd, [{
585+
x: [1, 2, 3, 4, 5, 6, 7],
586+
y: [2, 3, 4, 5, 6, 7, 8],
587+
fill: 'tozeroy',
588+
fillcolor: 'rgb(255, 0, 0)',
589+
}]).then(function() {
590+
expect(fill()).toEqual('rgb(255, 0, 0)');
591+
return Plotly.animate(gd,
592+
[{data: [{fillcolor: 'rgb(0, 0, 255)'}]}],
593+
{frame: {duration: 0, redraw: false}}
594+
);
595+
}).then(function() {
596+
expect(fill()).toEqual('rgb(0, 0, 255)');
597+
}).catch(fail).then(done);
598+
});
578599
});
579600

580601
describe('scatter hoverPoints', function() {

test/jasmine/tests/svg_text_utils_test.js

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,49 @@ describe('svg+text utils', function() {
1818
.attr('transform', 'translate(50,50)');
1919
}
2020

21-
function assertAnchorLink(node, href) {
21+
function assertAnchorLink(node, href, target, show, msg) {
2222
var a = node.select('a');
2323

24-
expect(a.attr('xlink:href')).toBe(href);
25-
expect(a.attr('xlink:show')).toBe(href === null ? null : 'new');
24+
if(target === undefined) target = href === null ? null : '_blank';
25+
if(show === undefined) show = href === null ? null : 'new';
26+
27+
expect(a.attr('xlink:href')).toBe(href, msg);
28+
expect(a.attr('target')).toBe(target, msg);
29+
expect(a.attr('xlink:show')).toBe(show, msg);
2630
}
2731

28-
function assertTspanStyle(node, style) {
32+
function assertTspanStyle(node, style, msg) {
2933
var tspan = node.select('tspan');
30-
expect(tspan.attr('style')).toBe(style);
34+
expect(tspan.attr('style')).toBe(style, msg);
3135
}
3236

33-
function assertAnchorAttrs(node, style) {
37+
function assertAnchorAttrs(node, expectedAttrs, msg) {
3438
var a = node.select('a');
3539

36-
var WHITE_LIST = ['xlink:href', 'xlink:show', 'style'],
40+
if(!expectedAttrs) expectedAttrs = {};
41+
42+
var WHITE_LIST = ['xlink:href', 'xlink:show', 'style', 'target', 'onclick'],
3743
attrs = listAttributes(a.node());
3844

3945
// check that no other attribute are found in anchor,
4046
// which can be lead to XSS attacks.
4147

42-
var hasWrongAttr = attrs.some(function(attr) {
43-
return WHITE_LIST.indexOf(attr) === -1;
48+
var wrongAttrs = [];
49+
attrs.forEach(function(attr) {
50+
if(WHITE_LIST.indexOf(attr) === -1) wrongAttrs.push(attr);
4451
});
4552

46-
expect(hasWrongAttr).toBe(false);
53+
expect(wrongAttrs).toEqual([], msg);
4754

55+
var style = expectedAttrs.style || '';
4856
var fullStyle = style || '';
4957
if(style) fullStyle += ';';
5058
fullStyle += 'cursor:pointer';
5159

52-
expect(a.attr('style')).toBe(fullStyle);
60+
expect(a.attr('style')).toBe(fullStyle, msg);
61+
62+
expect(a.attr('onclick')).toBe(expectedAttrs.onclick || null, msg);
63+
5364
}
5465

5566
function listAttributes(node) {
@@ -137,7 +148,7 @@ describe('svg+text utils', function() {
137148
var node = mockTextSVGElement(textCase);
138149

139150
expect(node.text()).toEqual('Subtitle');
140-
assertAnchorAttrs(node, 'font-size:300px');
151+
assertAnchorAttrs(node, {style: 'font-size:300px'});
141152
assertAnchorLink(node, 'XSS');
142153
});
143154
});
@@ -157,11 +168,31 @@ describe('svg+text utils', function() {
157168
var node = mockTextSVGElement(textCase);
158169

159170
expect(node.text()).toEqual('z');
160-
assertAnchorAttrs(node, 'y');
171+
assertAnchorAttrs(node, {style: 'y'});
161172
assertAnchorLink(node, 'x');
162173
});
163174
});
164175

176+
it('accepts `target` with links and tries to translate it to `xlink:show`', function() {
177+
var specs = [
178+
{target: '_blank', show: 'new'},
179+
{target: '_self', show: 'replace'},
180+
{target: '_parent', show: 'replace'},
181+
{target: '_top', show: 'replace'},
182+
{target: 'some_frame_name', show: 'new'}
183+
];
184+
specs.forEach(function(spec) {
185+
var node = mockTextSVGElement('<a href="x" target="' + spec.target + '">link</a>');
186+
assertAnchorLink(node, 'x', spec.target, spec.show, spec.target);
187+
});
188+
});
189+
190+
it('attaches onclick if popup is specified', function() {
191+
var node = mockTextSVGElement('<a href="x" target="fred" popup="width=500,height=400">link</a>');
192+
assertAnchorLink(node, 'x', 'fred', 'new');
193+
assertAnchorAttrs(node, {onclick: 'window.open(\'x\',\'fred\',\'width=500,height=400\');return false;'});
194+
});
195+
165196
it('keeps query parameters in href', function() {
166197
var textCases = [
167198
'<a href="https://abc.com/myFeature.jsp?name=abc&pwd=def">abc.com?shared-key</a>',
@@ -171,9 +202,9 @@ describe('svg+text utils', function() {
171202
textCases.forEach(function(textCase) {
172203
var node = mockTextSVGElement(textCase);
173204

174-
assertAnchorAttrs(node);
175-
expect(node.text()).toEqual('abc.com?shared-key');
176-
assertAnchorLink(node, 'https://abc.com/myFeature.jsp?name=abc&pwd=def');
205+
assertAnchorAttrs(node, {}, textCase);
206+
expect(node.text()).toEqual('abc.com?shared-key', textCase);
207+
assertAnchorLink(node, 'https://abc.com/myFeature.jsp?name=abc&pwd=def', undefined, undefined, textCase);
177208
});
178209
});
179210

test/jasmine/tests/transform_sort_test.js

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ describe('Test sort transform interactions:', function() {
258258
.then(done);
259259
});
260260

261-
it('does not preserve hover/click `pointNumber` value', function(done) {
261+
it('does not preserve event data `pointNumber` value', function(done) {
262262
var gd = createGraphDiv();
263263

264264
function getPxPos(gd, id) {
@@ -273,32 +273,18 @@ describe('Test sort transform interactions:', function() {
273273
}
274274

275275
function hover(gd, id) {
276-
return new Promise(function(resolve) {
276+
return new Promise(function(resolve, reject) {
277277
gd.once('plotly_hover', function(eventData) {
278+
delete gd._lastHoverTime;
278279
resolve(eventData);
279280
});
280281

281282
var pos = getPxPos(gd, id);
282283
mouseEvent('mousemove', pos[0], pos[1]);
283-
});
284-
}
285-
286-
function click(gd, id) {
287-
return new Promise(function(resolve) {
288-
gd.once('plotly_click', function(eventData) {
289-
resolve(eventData);
290-
});
291-
292-
var pos = getPxPos(gd, id);
293-
mouseEvent('mousemove', pos[0], pos[1]);
294-
mouseEvent('mousedown', pos[0], pos[1]);
295-
mouseEvent('mouseup', pos[0], pos[1]);
296-
});
297-
}
298284

299-
function wait() {
300-
return new Promise(function(resolve) {
301-
setTimeout(resolve, 100);
285+
setTimeout(function() {
286+
reject('plotly_hover did not get called!');
287+
}, 100);
302288
});
303289
}
304290

@@ -334,21 +320,18 @@ describe('Test sort transform interactions:', function() {
334320
.then(function(eventData) {
335321
assertPt(eventData, 0, 1, 3, 'D');
336322
})
337-
.then(wait)
338-
.then(function() { return click(gd, 'G'); })
323+
.then(function() { return hover(gd, 'G'); })
339324
.then(function(eventData) {
340325
assertPt(eventData, 1, 1, 6, 'G');
341326
})
342-
.then(wait)
343327
.then(function() {
344328
return Plotly.restyle(gd, 'transforms[0].enabled', true);
345329
})
346330
.then(function() { return hover(gd, 'D'); })
347331
.then(function(eventData) {
348332
assertPt(eventData, 0, 1, 1, 'D');
349333
})
350-
.then(wait)
351-
.then(function() { return click(gd, 'G'); })
334+
.then(function() { return hover(gd, 'G'); })
352335
.then(function(eventData) {
353336
assertPt(eventData, 1, 1, 5, 'G');
354337
})
@@ -359,8 +342,7 @@ describe('Test sort transform interactions:', function() {
359342
.then(function(eventData) {
360343
assertPt(eventData, 0, 1, 1, 'D');
361344
})
362-
.then(wait)
363-
.then(function() { return click(gd, 'G'); })
345+
.then(function() { return hover(gd, 'G'); })
364346
.then(function(eventData) {
365347
assertPt(eventData, 1, 1, 5, 'G');
366348
})

0 commit comments

Comments
 (0)