Skip to content

Commit 3aa9559

Browse files
authored
Merge pull request #6101 from s417-lama/pattern_fill_scatter
Add pattern fill for scatter filled area
2 parents d5f03f9 + 8ca0955 commit 3aa9559

File tree

11 files changed

+224
-60
lines changed

11 files changed

+224
-60
lines changed

draftlogs/6101_add.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add pattern fill for scatter filled area

src/components/drawing/index.js

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -177,24 +177,42 @@ drawing.dashStyle = function(dash, lineWidth) {
177177
return dash;
178178
};
179179

180+
function setFillStyle(sel, trace, gd) {
181+
var markerPattern = trace.fillpattern;
182+
var patternShape = markerPattern && drawing.getPatternAttr(markerPattern.shape, 0, '');
183+
if(patternShape) {
184+
var patternBGColor = drawing.getPatternAttr(markerPattern.bgcolor, 0, null);
185+
var patternFGColor = drawing.getPatternAttr(markerPattern.fgcolor, 0, null);
186+
var patternFGOpacity = markerPattern.fgopacity;
187+
var patternSize = drawing.getPatternAttr(markerPattern.size, 0, 8);
188+
var patternSolidity = drawing.getPatternAttr(markerPattern.solidity, 0, 0.3);
189+
var patternID = trace.uid;
190+
drawing.pattern(sel, 'point', gd, patternID,
191+
patternShape, patternSize, patternSolidity,
192+
undefined, markerPattern.fillmode,
193+
patternBGColor, patternFGColor, patternFGOpacity
194+
);
195+
} else if(trace.fillcolor) {
196+
sel.call(Color.fill, trace.fillcolor);
197+
}
198+
}
199+
180200
// Same as fillGroupStyle, except in this case the selection may be a transition
181-
drawing.singleFillStyle = function(sel) {
201+
drawing.singleFillStyle = function(sel, gd) {
182202
var node = d3.select(sel.node());
183203
var data = node.data();
184-
var fillcolor = (((data[0] || [])[0] || {}).trace || {}).fillcolor;
185-
if(fillcolor) {
186-
sel.call(Color.fill, fillcolor);
187-
}
204+
var trace = ((data[0] || [])[0] || {}).trace || {};
205+
setFillStyle(sel, trace, gd);
188206
};
189207

190-
drawing.fillGroupStyle = function(s) {
208+
drawing.fillGroupStyle = function(s, gd) {
191209
s.style('stroke-width', 0)
192210
.each(function(d) {
193211
var shape = d3.select(this);
194212
// N.B. 'd' won't be a calcdata item when
195213
// fill !== 'none' on a segment-less and marker-less trace
196214
if(d[0].trace) {
197-
shape.call(Color.fill, d[0].trace.fillcolor);
215+
setFillStyle(shape, d[0].trace, gd);
198216
}
199217
});
200218
};
@@ -347,12 +365,7 @@ drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) {
347365
sel.style(prop, getFullUrl(fullID, gd))
348366
.style(prop + '-opacity', null);
349367

350-
var className2query = function(s) {
351-
return '.' + s.attr('class').replace(/\s/g, '.');
352-
};
353-
var k = className2query(d3.select(sel.node().parentNode)) +
354-
'>' + className2query(sel);
355-
fullLayout._gradientUrlQueryParts[k] = 1;
368+
sel.classed('gradient_filled', true);
356369
};
357370

358371
/**
@@ -559,11 +572,6 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity,
559572
.style('fill-opacity', null);
560573

561574
sel.classed('pattern_filled', true);
562-
var className2query = function(s) {
563-
return '.' + s.attr('class').replace(/\s/g, '.');
564-
};
565-
var k = className2query(d3.select(sel.node().parentNode)) + '>.pattern_filled';
566-
fullLayout._patternUrlQueryParts[k] = 1;
567575
};
568576

569577
/*
@@ -579,9 +587,7 @@ drawing.initGradients = function(gd) {
579587
var gradientsGroup = Lib.ensureSingle(fullLayout._defs, 'g', 'gradients');
580588
gradientsGroup.selectAll('linearGradient,radialGradient').remove();
581589

582-
// initialize stash of query parts filled in Drawing.gradient,
583-
// used to fix URL strings during image exports
584-
fullLayout._gradientUrlQueryParts = {};
590+
d3.select(gd).selectAll('.gradient_filled').classed('gradient_filled', false);
585591
};
586592

587593
drawing.initPatterns = function(gd) {
@@ -590,9 +596,7 @@ drawing.initPatterns = function(gd) {
590596
var patternsGroup = Lib.ensureSingle(fullLayout._defs, 'g', 'patterns');
591597
patternsGroup.selectAll('pattern').remove();
592598

593-
// initialize stash of query parts filled in Drawing.pattern,
594-
// used to fix URL strings during image exports
595-
fullLayout._patternUrlQueryParts = {};
599+
d3.select(gd).selectAll('.pattern_filled').classed('pattern_filled', false);
596600
};
597601

598602
drawing.getPatternAttr = function(mp, i, dflt) {

src/components/legend/style.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,16 @@ module.exports = function style(s, gd, legend) {
111111
var colorscale = cOpts.colorscale;
112112
var reversescale = cOpts.reversescale;
113113

114-
var fillGradient = function(s) {
114+
var fillStyle = function(s) {
115115
if(s.size()) {
116-
var gradientID = 'legendfill-' + trace.uid;
117-
Drawing.gradient(s, gd, gradientID,
118-
getGradientDirection(reversescale),
119-
colorscale, 'fill');
116+
if(showFill) {
117+
Drawing.fillGroupStyle(s, gd);
118+
} else {
119+
var gradientID = 'legendfill-' + trace.uid;
120+
Drawing.gradient(s, gd, gradientID,
121+
getGradientDirection(reversescale),
122+
colorscale, 'fill');
123+
}
120124
}
121125
};
122126

@@ -145,7 +149,7 @@ module.exports = function style(s, gd, legend) {
145149
fill.enter().append('path').classed('js-fill', true);
146150
fill.exit().remove();
147151
fill.attr('d', pathStart + 'h' + itemWidth + 'v6h-' + itemWidth + 'z')
148-
.call(showFill ? Drawing.fillGroupStyle : fillGradient);
152+
.call(fillStyle);
149153

150154
if(showLine || showGradientLine) {
151155
var lw = boundLineWidth(undefined, trace.line, MAX_LINE_WIDTH, CST_LINE_WIDTH);

src/snapshot/tosvg.js

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = function toSVG(gd, format, scale) {
3333
var toppaper = fullLayout._toppaper;
3434
var width = fullLayout.width;
3535
var height = fullLayout.height;
36-
var i, k;
36+
var i;
3737

3838
// make background color a rect in the svg, then revert after scraping
3939
// all other alterations have been dealt with by properly preparing the svg
@@ -106,32 +106,21 @@ module.exports = function toSVG(gd, format, scale) {
106106
}
107107
});
108108

109-
var queryParts = [];
110-
if(fullLayout._gradientUrlQueryParts) {
111-
for(k in fullLayout._gradientUrlQueryParts) queryParts.push(k);
112-
}
113-
114-
if(fullLayout._patternUrlQueryParts) {
115-
for(k in fullLayout._patternUrlQueryParts) queryParts.push(k);
116-
}
109+
svg.selectAll('.gradient_filled,.pattern_filled').each(function() {
110+
var pt = d3.select(this);
117111

118-
if(queryParts.length) {
119-
svg.selectAll(queryParts.join(',')).each(function() {
120-
var pt = d3.select(this);
121-
122-
// similar to font family styles above,
123-
// we must remove " after the SVG DOM has been serialized
124-
var fill = this.style.fill;
125-
if(fill && fill.indexOf('url(') !== -1) {
126-
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
127-
}
112+
// similar to font family styles above,
113+
// we must remove " after the SVG DOM has been serialized
114+
var fill = this.style.fill;
115+
if(fill && fill.indexOf('url(') !== -1) {
116+
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
117+
}
128118

129-
var stroke = this.style.stroke;
130-
if(stroke && stroke.indexOf('url(') !== -1) {
131-
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
132-
}
133-
});
134-
}
119+
var stroke = this.style.stroke;
120+
if(stroke && stroke.indexOf('url(') !== -1) {
121+
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
122+
}
123+
});
135124

136125
if(format === 'pdf' || format === 'eps') {
137126
// these formats make the extra line MathJax adds around symbols look super thick in some cases

src/traces/scatter/attributes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplat
66
var colorScaleAttrs = require('../../components/colorscale/attributes');
77
var fontAttrs = require('../../plots/font_attributes');
88
var dash = require('../../components/drawing/attributes').dash;
9+
var pattern = require('../../components/drawing/attributes').pattern;
910

1011
var Drawing = require('../../components/drawing');
1112
var constants = require('./constants');
@@ -363,6 +364,7 @@ module.exports = {
363364
'marker color, or marker line color, whichever is available.'
364365
].join(' ')
365366
},
367+
fillpattern: pattern,
366368
marker: extendFlat({
367369
symbol: {
368370
valType: 'enumerated',

src/traces/scatter/defaults.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var handleLineDefaults = require('./line_defaults');
1414
var handleLineShapeDefaults = require('./line_shape_defaults');
1515
var handleTextDefaults = require('./text_defaults');
1616
var handleFillColorDefaults = require('./fillcolor_defaults');
17+
var coercePattern = require('../../lib').coercePattern;
1718

1819
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
1920
function coerce(attr, dflt) {
@@ -67,6 +68,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
6768
if(traceOut.fill !== 'none') {
6869
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
6970
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
71+
coercePattern(coerce, 'fillpattern', traceOut.fillcolor, false);
7072
}
7173

7274
var lineColor = (traceOut.line || {}).color;

src/traces/scatter/plot.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,11 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
304304
// the points on the axes are the first two points. Otherwise
305305
// animations get a little crazy if the number of points changes.
306306
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1))
307-
.call(Drawing.singleFillStyle);
307+
.call(Drawing.singleFillStyle, gd);
308308
} else {
309309
// fill to self: just join the path to itself
310310
transition(ownFillEl3).attr('d', fullpath + 'Z')
311-
.call(Drawing.singleFillStyle);
311+
.call(Drawing.singleFillStyle, gd);
312312
}
313313
}
314314
} else if(tonext) {
@@ -320,15 +320,15 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
320320
// This makes strange results if one path is *not* entirely
321321
// inside the other, but then that is a strange usage.
322322
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z')
323-
.call(Drawing.singleFillStyle);
323+
.call(Drawing.singleFillStyle, gd);
324324
} else {
325325
// tonextx/y: for now just connect endpoints with lines. This is
326326
// the correct behavior if the endpoints are at the same value of
327327
// y/x, but if they *aren't*, we should ideally do more complicated
328328
// things depending on whether the new endpoint projects onto the
329329
// existing curve or off the end of it
330330
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z')
331-
.call(Drawing.singleFillStyle);
331+
.call(Drawing.singleFillStyle, gd);
332332
}
333333
trace._polygons = trace._polygons.concat(prevPolygons);
334334
} else {

src/traces/scatter/style.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function style(gd) {
2727
.call(Drawing.lineGroupStyle);
2828

2929
s.selectAll('g.trace path.js-fill')
30-
.call(Drawing.fillGroupStyle);
30+
.call(Drawing.fillGroupStyle, gd);
3131

3232
Registry.getComponentMethod('errorbars', 'style')(s);
3333
}
72.3 KB
Loading
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"data": [
3+
{
4+
"x": [1, 2, 3, 4, 5],
5+
"y": [0.1, 0.3, 0.2, 0.8, 0.7],
6+
"stackgroup": "one",
7+
"fillpattern": {
8+
"fillmode": "overlay",
9+
"shape": "/"
10+
}
11+
},
12+
{
13+
"x": [1, 2, 3, 4, 5],
14+
"y": [0.3, 0.2, 0.1, 0.1, 0.2],
15+
"stackgroup": "one",
16+
"fillpattern": {
17+
"fillmode": "overlay",
18+
"shape": "\\"
19+
}
20+
},
21+
{
22+
"x": [1, 2, 3, 4, 5],
23+
"y": [0.8, 0.8, 0.6, 0.7, 0.4],
24+
"stackgroup": "one",
25+
"fillpattern": {
26+
"fillmode": "overlay",
27+
"shape": "."
28+
}
29+
},
30+
31+
{
32+
"xaxis": "x2",
33+
"yaxis": "y2",
34+
"x": [1, 2, 3, 4, 5, 6],
35+
"y": [0.1, 0.3, 0.4, 1.1, 0.8, 0.3],
36+
"fill": "tozeroy",
37+
"fillpattern": {
38+
"fillmode": "replace",
39+
"solidity": 0.4,
40+
"size": 12,
41+
"shape": "-"
42+
}
43+
},
44+
{
45+
"xaxis": "x2",
46+
"yaxis": "y2",
47+
"x": [1, 2, 3, 4, 5, 6],
48+
"y": [0.8, 0.7, 0.1, 0.6, 0.7, 0.8],
49+
"fill": "tonexty",
50+
"fillpattern": {
51+
"fillmode": "replace",
52+
"solidity": 0.4,
53+
"size": 12,
54+
"shape": "|"
55+
}
56+
}
57+
],
58+
"layout": {
59+
"title": {"text": "Pattern fill for scatter"},
60+
"width": 800,
61+
"height": 400,
62+
63+
"grid": {
64+
"rows": 1,
65+
"columns": 2,
66+
"pattern": "independent"
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)