Skip to content

Commit

Permalink
Merge pull request #5733 from plotly/pattern-fgcolor-and-overlay-fill…
Browse files Browse the repository at this point in the history
…mode

Implement fgopacity, fgcolor & "overlay" fillmode for bars, fix bar legend and pattern with marker colorscale
  • Loading branch information
archmoj authored Jun 18, 2021
2 parents afa625c + 178e0a6 commit 16a14f5
Show file tree
Hide file tree
Showing 14 changed files with 665 additions and 30 deletions.
42 changes: 39 additions & 3 deletions src/components/drawing/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,46 @@ exports.pattern = {
'By default, no pattern is used for filling the area.',
].join(' ')
},
fillmode: {
valType: 'enumerated',
values: ['replace', 'overlay'],
dflt: 'replace',
editType: 'style',
description: [
'Determines whether `marker.color` should be used',
'as a default to `bgcolor` or a `fgcolor`.'
].join(' ')
},
bgcolor: {
valType: 'color',
arrayOk: true,
editType: 'style',
description: [
'Sets the background color of the pattern fill.',
'Defaults to a transparent background.',
'When there is no colorscale sets the color of background pattern fill.',
'Defaults to a `marker.color` background when `fillmode` is *overlay*.',
'Otherwise, defaults to a transparent background.'
].join(' ')
},
fgcolor: {
valType: 'color',
arrayOk: true,
editType: 'style',
description: [
'When there is no colorscale sets the color of foreground pattern fill.',
'Defaults to a `marker.color` background when `fillmode` is *replace*.',
'Otherwise, defaults to dark grey or white',
'to increase contrast with the `bgcolor`.',
].join(' ')
},
fgopacity: {
valType: 'number',
editType: 'style',
min: 0,
max: 1,
description: [
'Sets the opacity of the foreground pattern fill.',
'Defaults to a 0.5 when `fillmode` is *overlay*.',
'Otherwise, defaults to 1.'
].join(' ')
},
size: {
Expand Down Expand Up @@ -62,5 +95,8 @@ exports.pattern = {
'and solidty of 1 shows only the foreground color without pattern.',
].join(' ')
},
editType: 'style'
editType: 'style',
description: [
'Sets the pattern within the marker.'
].join(' '),
};
67 changes: 53 additions & 14 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,16 +359,31 @@ drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) {
*
* @param {object} sel: d3 selection to apply this pattern to
* You can use `selection.call(Drawing.pattern, ...)`
* @param {string} calledBy: option to know the caller component
* @param {DOM element} gd: the graph div `sel` is part of
* @param {string} patternID: a unique (within this plot) identifier
* for this pattern, so that we don't create unnecessary definitions
* @param {string} bgcolor: background color for this pattern
* @param {string} fgcolor: foreground color for this pattern
* @param {number} size: size of unit squares for repetition of this pattern
* @param {number} solidity: how solid lines of this pattern are
* @param {string} prop: the property to apply to, 'fill' or 'stroke'
* @param {string} mcc: color when painted with colorscale
* @param {string} fillmode: fillmode for this pattern
* @param {string} bgcolor: background color for this pattern
* @param {string} fgcolor: foreground color for this pattern
* @param {number} fgopacity: foreground opacity for this pattern
*/
drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, solidity, prop) {
drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, mcc, fillmode, bgcolor, fgcolor, fgopacity) {
var isLegend = calledBy === 'legend';

if(mcc) {
if(fillmode === 'overlay') {
bgcolor = mcc;
fgcolor = Color.contrast(bgcolor);
} else {
bgcolor = undefined;
fgcolor = mcc;
}
}

var fullLayout = gd._fullLayout;
var fullID = 'p' + fullLayout._uid + '-' + patternID;
var width, height;
Expand All @@ -392,6 +407,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
patternTag = 'path';
patternAttrs = {
'd': path,
'opacity': fgopacity,
'stroke': fgcolor,
'stroke-width': linewidth + 'px'
};
Expand All @@ -406,6 +422,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
patternTag = 'path';
patternAttrs = {
'd': path,
'opacity': fgopacity,
'stroke': fgcolor,
'stroke-width': linewidth + 'px'
};
Expand All @@ -423,6 +440,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
patternTag = 'path';
patternAttrs = {
'd': path,
'opacity': fgopacity,
'stroke': fgcolor,
'stroke-width': linewidth + 'px'
};
Expand All @@ -436,6 +454,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
patternTag = 'path';
patternAttrs = {
'd': path,
'opacity': fgopacity,
'stroke': fgcolor,
'stroke-width': linewidth + 'px'
};
Expand All @@ -449,6 +468,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
patternTag = 'path';
patternAttrs = {
'd': path,
'opacity': fgopacity,
'stroke': fgcolor,
'stroke-width': linewidth + 'px'
};
Expand All @@ -463,6 +483,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
patternTag = 'path';
patternAttrs = {
'd': path,
'opacity': fgopacity,
'stroke': fgcolor,
'stroke-width': linewidth + 'px'
};
Expand All @@ -480,14 +501,23 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
'cx': width / 2,
'cy': height / 2,
'r': radius,
'opacity': fgopacity,
'fill': fgcolor
};
break;
}

var str = [
shape || 'noSh',
bgcolor || 'noBg',
fgcolor || 'noFg',
size,
solidity
].join(';');

var pattern = fullLayout._defs.select('.patterns')
.selectAll('#' + fullID)
.data([shape + ';' + bgcolor + ';' + fgcolor + ';' + size + ';' + solidity], Lib.identity);
.data([str], Lib.identity);

pattern.exit().remove();

Expand All @@ -500,7 +530,9 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
'id': fullID,
'width': width + 'px',
'height': height + 'px',
'patternUnits': 'userSpaceOnUse'
'patternUnits': 'userSpaceOnUse',
// for legends scale down patterns just a bit so that default size (i.e 8) nicely fit in small icons
'patternTransform': isLegend ? 'scale(0.8)' : ''
});

if(bgcolor) {
Expand All @@ -522,8 +554,8 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
.attr(patternAttrs);
});

sel.style(prop, getFullUrl(fullID, gd))
.style(prop + '-opacity', null);
sel.style('fill', getFullUrl(fullID, gd))
.style('fill-opacity', null);

sel.classed('pattern_filled', true);
var className2query = function(s) {
Expand Down Expand Up @@ -693,18 +725,25 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) {
[[0, gradientColor], [1, fillColor]], 'fill');
} else if(patternShape) {
var patternBGColor = drawing.getPatternAttr(markerPattern.bgcolor, d.i, null);
var patternFGColor = drawing.getPatternAttr(markerPattern.fgcolor, d.i, null);
var patternFGOpacity = markerPattern.fgopacity;
var patternSize = drawing.getPatternAttr(markerPattern.size, d.i, 8);
var patternSolidity = drawing.getPatternAttr(markerPattern.solidity, d.i, 0.3);
var perPointPattern = Lib.isArrayOrTypedArray(markerPattern.shape) ||
Lib.isArrayOrTypedArray(markerPattern.bgcolor) ||
Lib.isArrayOrTypedArray(markerPattern.size) ||
Lib.isArrayOrTypedArray(markerPattern.solidity);
var perPointPattern = d.mcc ||
Lib.isArrayOrTypedArray(markerPattern.shape) ||
Lib.isArrayOrTypedArray(markerPattern.bgcolor) ||
Lib.isArrayOrTypedArray(markerPattern.size) ||
Lib.isArrayOrTypedArray(markerPattern.solidity);

var patternID = trace.uid;
if(perPointPattern) patternID += '-' + d.i;

drawing.pattern(sel, gd, patternID, patternShape, patternBGColor, fillColor,
patternSize, patternSolidity, 'fill');
drawing.pattern(
sel, 'point', gd, patternID,
patternShape, patternSize, patternSolidity,
d.mcc, markerPattern.fillmode,
patternBGColor, patternFGColor, patternFGOpacity
);
} else {
Color.fill(sel, fillColor);
}
Expand Down
31 changes: 26 additions & 5 deletions src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,18 +348,33 @@ module.exports = function style(s, gd, legend) {

p.style('stroke-width', w + 'px');

var fillColor = d0.mc || marker.color;
var mcc = d0.mcc;
if(!legend._inHover && 'mc' in d0) {
// not in unified hover but
// for legend use the color in the middle of scale
var cOpts = extractOpts(marker);
var mid = cOpts.mid;
if(mid === undefined) mid = (cOpts.max + cOpts.min) / 2;
mcc = Drawing.tryColorscale(marker, '')(mid);
}
var fillColor = mcc || d0.mc || marker.color;

var markerPattern = marker.pattern;
var patternShape = markerPattern && Drawing.getPatternAttr(markerPattern.shape, 0, '');

if(patternShape) {
var patternBGColor = Drawing.getPatternAttr(markerPattern.bgcolor, 0, null);
var patternSize = Math.min(12, Drawing.getPatternAttr(markerPattern.size, 0, 8));
var patternSolidity = Drawing.getPatternAttr(markerPattern.solidity, 0, 0.3);
var patternFGColor = Drawing.getPatternAttr(markerPattern.fgcolor, 0, null);
var patternFGOpacity = markerPattern.fgopacity;
var patternSize = dimAttr(markerPattern.size, 8, 10);
var patternSolidity = dimAttr(markerPattern.solidity, 0.5, 1);
var patternID = 'legend-' + trace.uid;
p.call(Drawing.pattern, gd, patternID, patternShape, patternBGColor,
fillColor, patternSize, patternSolidity, 'fill');
p.call(
Drawing.pattern, 'legend', gd, patternID,
patternShape, patternSize, patternSolidity,
mcc, markerPattern.fillmode,
patternBGColor, patternFGColor, patternFGOpacity
);
} else {
p.call(Color.fill, fillColor);
}
Expand Down Expand Up @@ -662,3 +677,9 @@ function getStyleGuide(d) {
anyFill: showFill || showGradientFill,
};
}

function dimAttr(v, dflt, max) {
if(v && Lib.isArrayOrTypedArray(v)) return dflt;
if(v > max) return max;
return v;
}
25 changes: 22 additions & 3 deletions src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var tinycolor = require('tinycolor2');

var baseTraceAttrs = require('../plots/attributes');
var colorscales = require('../components/colorscale/scales');
var Color = require('../components/color');
var DESELECTDIM = require('../constants/interactions').DESELECTDIM;

var nestedProperty = require('./nested_property');
Expand Down Expand Up @@ -427,12 +428,30 @@ exports.coerceFont = function(coerce, attr, dfltObj) {
/*
* Shortcut to coerce the pattern attributes
*/
exports.coercePattern = function(coerce, attr) {
exports.coercePattern = function(coerce, attr, markerColor, hasMarkerColorscale) {
var shape = coerce(attr + '.shape');
if(shape) {
coerce(attr + '.size');
coerce(attr + '.bgcolor');
coerce(attr + '.solidity');
coerce(attr + '.size');
var fillmode = coerce(attr + '.fillmode');
var isOverlay = fillmode === 'overlay';

if(!hasMarkerColorscale) {
var bgcolor = coerce(attr + '.bgcolor', isOverlay ?
markerColor :
undefined
);

coerce(attr + '.fgcolor', isOverlay ?
Color.contrast(bgcolor) :
markerColor
);
}

coerce(attr + '.fgopacity', isOverlay ?
0.5 :
1
);
}
};

Expand Down
9 changes: 4 additions & 5 deletions src/traces/bar/style_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ var colorscaleDefaults = require('../../components/colorscale/defaults');
var coercePattern = require('../../lib').coercePattern;

module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) {
coerce('marker.color', defaultColor);

if(hasColorscale(traceIn, 'marker')) {
var markerColor = coerce('marker.color', defaultColor);
var hasMarkerColorscale = hasColorscale(traceIn, 'marker');
if(hasMarkerColorscale) {
colorscaleDefaults(
traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}
);
Expand All @@ -24,8 +24,7 @@ module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, default

coerce('marker.line.width');
coerce('marker.opacity');
coercePattern(coerce, 'marker.pattern');

coercePattern(coerce, 'marker.pattern', markerColor, hasMarkerColorscale);
coerce('selected.marker.color');
coerce('unselected.marker.color');
};
2 changes: 2 additions & 0 deletions tasks/test_mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ function notBlackListed(name) {
'mathjax',
'ohlc_first',
'pattern_bars',
'pattern_fgcolor_overlay_fillmode',
'pattern_with_colorscale',
'plot_types',
'polar_blank',
'polar_dates',
Expand Down
Binary file modified test/image/baselines/pattern_bars.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/pattern_with_colorscale.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 16a14f5

Please sign in to comment.