Skip to content

Commit

Permalink
Merge pull request #6101 from s417-lama/pattern_fill_scatter
Browse files Browse the repository at this point in the history
Add pattern fill for scatter filled area
  • Loading branch information
archmoj authored Feb 18, 2022
2 parents d5f03f9 + 8ca0955 commit 3aa9559
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 60 deletions.
1 change: 1 addition & 0 deletions draftlogs/6101_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add pattern fill for scatter filled area
52 changes: 28 additions & 24 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,24 +177,42 @@ drawing.dashStyle = function(dash, lineWidth) {
return dash;
};

function setFillStyle(sel, trace, gd) {
var markerPattern = trace.fillpattern;
var patternShape = markerPattern && drawing.getPatternAttr(markerPattern.shape, 0, '');
if(patternShape) {
var patternBGColor = drawing.getPatternAttr(markerPattern.bgcolor, 0, null);
var patternFGColor = drawing.getPatternAttr(markerPattern.fgcolor, 0, null);
var patternFGOpacity = markerPattern.fgopacity;
var patternSize = drawing.getPatternAttr(markerPattern.size, 0, 8);
var patternSolidity = drawing.getPatternAttr(markerPattern.solidity, 0, 0.3);
var patternID = trace.uid;
drawing.pattern(sel, 'point', gd, patternID,
patternShape, patternSize, patternSolidity,
undefined, markerPattern.fillmode,
patternBGColor, patternFGColor, patternFGOpacity
);
} else if(trace.fillcolor) {
sel.call(Color.fill, trace.fillcolor);
}
}

// Same as fillGroupStyle, except in this case the selection may be a transition
drawing.singleFillStyle = function(sel) {
drawing.singleFillStyle = function(sel, gd) {
var node = d3.select(sel.node());
var data = node.data();
var fillcolor = (((data[0] || [])[0] || {}).trace || {}).fillcolor;
if(fillcolor) {
sel.call(Color.fill, fillcolor);
}
var trace = ((data[0] || [])[0] || {}).trace || {};
setFillStyle(sel, trace, gd);
};

drawing.fillGroupStyle = function(s) {
drawing.fillGroupStyle = function(s, gd) {
s.style('stroke-width', 0)
.each(function(d) {
var shape = d3.select(this);
// N.B. 'd' won't be a calcdata item when
// fill !== 'none' on a segment-less and marker-less trace
if(d[0].trace) {
shape.call(Color.fill, d[0].trace.fillcolor);
setFillStyle(shape, d[0].trace, gd);
}
});
};
Expand Down Expand Up @@ -347,12 +365,7 @@ drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) {
sel.style(prop, getFullUrl(fullID, gd))
.style(prop + '-opacity', null);

var className2query = function(s) {
return '.' + s.attr('class').replace(/\s/g, '.');
};
var k = className2query(d3.select(sel.node().parentNode)) +
'>' + className2query(sel);
fullLayout._gradientUrlQueryParts[k] = 1;
sel.classed('gradient_filled', true);
};

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

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

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

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

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

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

drawing.getPatternAttr = function(mp, i, dflt) {
Expand Down
16 changes: 10 additions & 6 deletions src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,16 @@ module.exports = function style(s, gd, legend) {
var colorscale = cOpts.colorscale;
var reversescale = cOpts.reversescale;

var fillGradient = function(s) {
var fillStyle = function(s) {
if(s.size()) {
var gradientID = 'legendfill-' + trace.uid;
Drawing.gradient(s, gd, gradientID,
getGradientDirection(reversescale),
colorscale, 'fill');
if(showFill) {
Drawing.fillGroupStyle(s, gd);
} else {
var gradientID = 'legendfill-' + trace.uid;
Drawing.gradient(s, gd, gradientID,
getGradientDirection(reversescale),
colorscale, 'fill');
}
}
};

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

if(showLine || showGradientLine) {
var lw = boundLineWidth(undefined, trace.line, MAX_LINE_WIDTH, CST_LINE_WIDTH);
Expand Down
39 changes: 14 additions & 25 deletions src/snapshot/tosvg.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports = function toSVG(gd, format, scale) {
var toppaper = fullLayout._toppaper;
var width = fullLayout.width;
var height = fullLayout.height;
var i, k;
var i;

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

var queryParts = [];
if(fullLayout._gradientUrlQueryParts) {
for(k in fullLayout._gradientUrlQueryParts) queryParts.push(k);
}

if(fullLayout._patternUrlQueryParts) {
for(k in fullLayout._patternUrlQueryParts) queryParts.push(k);
}
svg.selectAll('.gradient_filled,.pattern_filled').each(function() {
var pt = d3.select(this);

if(queryParts.length) {
svg.selectAll(queryParts.join(',')).each(function() {
var pt = d3.select(this);

// similar to font family styles above,
// we must remove " after the SVG DOM has been serialized
var fill = this.style.fill;
if(fill && fill.indexOf('url(') !== -1) {
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}
// similar to font family styles above,
// we must remove " after the SVG DOM has been serialized
var fill = this.style.fill;
if(fill && fill.indexOf('url(') !== -1) {
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}

var stroke = this.style.stroke;
if(stroke && stroke.indexOf('url(') !== -1) {
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}
});
}
var stroke = this.style.stroke;
if(stroke && stroke.indexOf('url(') !== -1) {
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}
});

if(format === 'pdf' || format === 'eps') {
// these formats make the extra line MathJax adds around symbols look super thick in some cases
Expand Down
2 changes: 2 additions & 0 deletions src/traces/scatter/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplat
var colorScaleAttrs = require('../../components/colorscale/attributes');
var fontAttrs = require('../../plots/font_attributes');
var dash = require('../../components/drawing/attributes').dash;
var pattern = require('../../components/drawing/attributes').pattern;

var Drawing = require('../../components/drawing');
var constants = require('./constants');
Expand Down Expand Up @@ -363,6 +364,7 @@ module.exports = {
'marker color, or marker line color, whichever is available.'
].join(' ')
},
fillpattern: pattern,
marker: extendFlat({
symbol: {
valType: 'enumerated',
Expand Down
2 changes: 2 additions & 0 deletions src/traces/scatter/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var handleLineDefaults = require('./line_defaults');
var handleLineShapeDefaults = require('./line_shape_defaults');
var handleTextDefaults = require('./text_defaults');
var handleFillColorDefaults = require('./fillcolor_defaults');
var coercePattern = require('../../lib').coercePattern;

module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
Expand Down Expand Up @@ -67,6 +68,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
coercePattern(coerce, 'fillpattern', traceOut.fillcolor, false);
}

var lineColor = (traceOut.line || {}).color;
Expand Down
8 changes: 4 additions & 4 deletions src/traces/scatter/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,11 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
// the points on the axes are the first two points. Otherwise
// animations get a little crazy if the number of points changes.
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1))
.call(Drawing.singleFillStyle);
.call(Drawing.singleFillStyle, gd);
} else {
// fill to self: just join the path to itself
transition(ownFillEl3).attr('d', fullpath + 'Z')
.call(Drawing.singleFillStyle);
.call(Drawing.singleFillStyle, gd);
}
}
} else if(tonext) {
Expand All @@ -320,15 +320,15 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
// This makes strange results if one path is *not* entirely
// inside the other, but then that is a strange usage.
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z')
.call(Drawing.singleFillStyle);
.call(Drawing.singleFillStyle, gd);
} else {
// tonextx/y: for now just connect endpoints with lines. This is
// the correct behavior if the endpoints are at the same value of
// y/x, but if they *aren't*, we should ideally do more complicated
// things depending on whether the new endpoint projects onto the
// existing curve or off the end of it
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z')
.call(Drawing.singleFillStyle);
.call(Drawing.singleFillStyle, gd);
}
trace._polygons = trace._polygons.concat(prevPolygons);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/traces/scatter/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function style(gd) {
.call(Drawing.lineGroupStyle);

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

Registry.getComponentMethod('errorbars', 'style')(s);
}
Expand Down
Binary file added test/image/baselines/z-scatter_fill_pattern.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions test/image/mocks/z-scatter_fill_pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"data": [
{
"x": [1, 2, 3, 4, 5],
"y": [0.1, 0.3, 0.2, 0.8, 0.7],
"stackgroup": "one",
"fillpattern": {
"fillmode": "overlay",
"shape": "/"
}
},
{
"x": [1, 2, 3, 4, 5],
"y": [0.3, 0.2, 0.1, 0.1, 0.2],
"stackgroup": "one",
"fillpattern": {
"fillmode": "overlay",
"shape": "\\"
}
},
{
"x": [1, 2, 3, 4, 5],
"y": [0.8, 0.8, 0.6, 0.7, 0.4],
"stackgroup": "one",
"fillpattern": {
"fillmode": "overlay",
"shape": "."
}
},

{
"xaxis": "x2",
"yaxis": "y2",
"x": [1, 2, 3, 4, 5, 6],
"y": [0.1, 0.3, 0.4, 1.1, 0.8, 0.3],
"fill": "tozeroy",
"fillpattern": {
"fillmode": "replace",
"solidity": 0.4,
"size": 12,
"shape": "-"
}
},
{
"xaxis": "x2",
"yaxis": "y2",
"x": [1, 2, 3, 4, 5, 6],
"y": [0.8, 0.7, 0.1, 0.6, 0.7, 0.8],
"fill": "tonexty",
"fillpattern": {
"fillmode": "replace",
"solidity": 0.4,
"size": 12,
"shape": "|"
}
}
],
"layout": {
"title": {"text": "Pattern fill for scatter"},
"width": 800,
"height": 400,

"grid": {
"rows": 1,
"columns": 2,
"pattern": "independent"
}
}
}
Loading

0 comments on commit 3aa9559

Please sign in to comment.