Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pattern fill for scatter filled area #6101

Merged
merged 5 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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