Skip to content

Commit 16a14f5

Browse files
authored
Merge pull request #5733 from plotly/pattern-fgcolor-and-overlay-fillmode
Implement fgopacity, fgcolor & "overlay" fillmode for bars, fix bar legend and pattern with marker colorscale
2 parents afa625c + 178e0a6 commit 16a14f5

14 files changed

+665
-30
lines changed

src/components/drawing/attributes.js

+39-3
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,46 @@ exports.pattern = {
2828
'By default, no pattern is used for filling the area.',
2929
].join(' ')
3030
},
31+
fillmode: {
32+
valType: 'enumerated',
33+
values: ['replace', 'overlay'],
34+
dflt: 'replace',
35+
editType: 'style',
36+
description: [
37+
'Determines whether `marker.color` should be used',
38+
'as a default to `bgcolor` or a `fgcolor`.'
39+
].join(' ')
40+
},
3141
bgcolor: {
3242
valType: 'color',
3343
arrayOk: true,
3444
editType: 'style',
3545
description: [
36-
'Sets the background color of the pattern fill.',
37-
'Defaults to a transparent background.',
46+
'When there is no colorscale sets the color of background pattern fill.',
47+
'Defaults to a `marker.color` background when `fillmode` is *overlay*.',
48+
'Otherwise, defaults to a transparent background.'
49+
].join(' ')
50+
},
51+
fgcolor: {
52+
valType: 'color',
53+
arrayOk: true,
54+
editType: 'style',
55+
description: [
56+
'When there is no colorscale sets the color of foreground pattern fill.',
57+
'Defaults to a `marker.color` background when `fillmode` is *replace*.',
58+
'Otherwise, defaults to dark grey or white',
59+
'to increase contrast with the `bgcolor`.',
60+
].join(' ')
61+
},
62+
fgopacity: {
63+
valType: 'number',
64+
editType: 'style',
65+
min: 0,
66+
max: 1,
67+
description: [
68+
'Sets the opacity of the foreground pattern fill.',
69+
'Defaults to a 0.5 when `fillmode` is *overlay*.',
70+
'Otherwise, defaults to 1.'
3871
].join(' ')
3972
},
4073
size: {
@@ -62,5 +95,8 @@ exports.pattern = {
6295
'and solidty of 1 shows only the foreground color without pattern.',
6396
].join(' ')
6497
},
65-
editType: 'style'
98+
editType: 'style',
99+
description: [
100+
'Sets the pattern within the marker.'
101+
].join(' '),
66102
};

src/components/drawing/index.js

+53-14
Original file line numberDiff line numberDiff line change
@@ -359,16 +359,31 @@ drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) {
359359
*
360360
* @param {object} sel: d3 selection to apply this pattern to
361361
* You can use `selection.call(Drawing.pattern, ...)`
362+
* @param {string} calledBy: option to know the caller component
362363
* @param {DOM element} gd: the graph div `sel` is part of
363364
* @param {string} patternID: a unique (within this plot) identifier
364365
* for this pattern, so that we don't create unnecessary definitions
365-
* @param {string} bgcolor: background color for this pattern
366-
* @param {string} fgcolor: foreground color for this pattern
367366
* @param {number} size: size of unit squares for repetition of this pattern
368367
* @param {number} solidity: how solid lines of this pattern are
369-
* @param {string} prop: the property to apply to, 'fill' or 'stroke'
368+
* @param {string} mcc: color when painted with colorscale
369+
* @param {string} fillmode: fillmode for this pattern
370+
* @param {string} bgcolor: background color for this pattern
371+
* @param {string} fgcolor: foreground color for this pattern
372+
* @param {number} fgopacity: foreground opacity for this pattern
370373
*/
371-
drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, solidity, prop) {
374+
drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, mcc, fillmode, bgcolor, fgcolor, fgopacity) {
375+
var isLegend = calledBy === 'legend';
376+
377+
if(mcc) {
378+
if(fillmode === 'overlay') {
379+
bgcolor = mcc;
380+
fgcolor = Color.contrast(bgcolor);
381+
} else {
382+
bgcolor = undefined;
383+
fgcolor = mcc;
384+
}
385+
}
386+
372387
var fullLayout = gd._fullLayout;
373388
var fullID = 'p' + fullLayout._uid + '-' + patternID;
374389
var width, height;
@@ -392,6 +407,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
392407
patternTag = 'path';
393408
patternAttrs = {
394409
'd': path,
410+
'opacity': fgopacity,
395411
'stroke': fgcolor,
396412
'stroke-width': linewidth + 'px'
397413
};
@@ -406,6 +422,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
406422
patternTag = 'path';
407423
patternAttrs = {
408424
'd': path,
425+
'opacity': fgopacity,
409426
'stroke': fgcolor,
410427
'stroke-width': linewidth + 'px'
411428
};
@@ -423,6 +440,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
423440
patternTag = 'path';
424441
patternAttrs = {
425442
'd': path,
443+
'opacity': fgopacity,
426444
'stroke': fgcolor,
427445
'stroke-width': linewidth + 'px'
428446
};
@@ -436,6 +454,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
436454
patternTag = 'path';
437455
patternAttrs = {
438456
'd': path,
457+
'opacity': fgopacity,
439458
'stroke': fgcolor,
440459
'stroke-width': linewidth + 'px'
441460
};
@@ -449,6 +468,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
449468
patternTag = 'path';
450469
patternAttrs = {
451470
'd': path,
471+
'opacity': fgopacity,
452472
'stroke': fgcolor,
453473
'stroke-width': linewidth + 'px'
454474
};
@@ -463,6 +483,7 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
463483
patternTag = 'path';
464484
patternAttrs = {
465485
'd': path,
486+
'opacity': fgopacity,
466487
'stroke': fgcolor,
467488
'stroke-width': linewidth + 'px'
468489
};
@@ -480,14 +501,23 @@ drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, so
480501
'cx': width / 2,
481502
'cy': height / 2,
482503
'r': radius,
504+
'opacity': fgopacity,
483505
'fill': fgcolor
484506
};
485507
break;
486508
}
487509

510+
var str = [
511+
shape || 'noSh',
512+
bgcolor || 'noBg',
513+
fgcolor || 'noFg',
514+
size,
515+
solidity
516+
].join(';');
517+
488518
var pattern = fullLayout._defs.select('.patterns')
489519
.selectAll('#' + fullID)
490-
.data([shape + ';' + bgcolor + ';' + fgcolor + ';' + size + ';' + solidity], Lib.identity);
520+
.data([str], Lib.identity);
491521

492522
pattern.exit().remove();
493523

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

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

525-
sel.style(prop, getFullUrl(fullID, gd))
526-
.style(prop + '-opacity', null);
557+
sel.style('fill', getFullUrl(fullID, gd))
558+
.style('fill-opacity', null);
527559

528560
sel.classed('pattern_filled', true);
529561
var className2query = function(s) {
@@ -693,18 +725,25 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) {
693725
[[0, gradientColor], [1, fillColor]], 'fill');
694726
} else if(patternShape) {
695727
var patternBGColor = drawing.getPatternAttr(markerPattern.bgcolor, d.i, null);
728+
var patternFGColor = drawing.getPatternAttr(markerPattern.fgcolor, d.i, null);
729+
var patternFGOpacity = markerPattern.fgopacity;
696730
var patternSize = drawing.getPatternAttr(markerPattern.size, d.i, 8);
697731
var patternSolidity = drawing.getPatternAttr(markerPattern.solidity, d.i, 0.3);
698-
var perPointPattern = Lib.isArrayOrTypedArray(markerPattern.shape) ||
699-
Lib.isArrayOrTypedArray(markerPattern.bgcolor) ||
700-
Lib.isArrayOrTypedArray(markerPattern.size) ||
701-
Lib.isArrayOrTypedArray(markerPattern.solidity);
732+
var perPointPattern = d.mcc ||
733+
Lib.isArrayOrTypedArray(markerPattern.shape) ||
734+
Lib.isArrayOrTypedArray(markerPattern.bgcolor) ||
735+
Lib.isArrayOrTypedArray(markerPattern.size) ||
736+
Lib.isArrayOrTypedArray(markerPattern.solidity);
702737

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

706-
drawing.pattern(sel, gd, patternID, patternShape, patternBGColor, fillColor,
707-
patternSize, patternSolidity, 'fill');
741+
drawing.pattern(
742+
sel, 'point', gd, patternID,
743+
patternShape, patternSize, patternSolidity,
744+
d.mcc, markerPattern.fillmode,
745+
patternBGColor, patternFGColor, patternFGOpacity
746+
);
708747
} else {
709748
Color.fill(sel, fillColor);
710749
}

src/components/legend/style.js

+26-5
Original file line numberDiff line numberDiff line change
@@ -348,18 +348,33 @@ module.exports = function style(s, gd, legend) {
348348

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

351-
var fillColor = d0.mc || marker.color;
351+
var mcc = d0.mcc;
352+
if(!legend._inHover && 'mc' in d0) {
353+
// not in unified hover but
354+
// for legend use the color in the middle of scale
355+
var cOpts = extractOpts(marker);
356+
var mid = cOpts.mid;
357+
if(mid === undefined) mid = (cOpts.max + cOpts.min) / 2;
358+
mcc = Drawing.tryColorscale(marker, '')(mid);
359+
}
360+
var fillColor = mcc || d0.mc || marker.color;
352361

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

356365
if(patternShape) {
357366
var patternBGColor = Drawing.getPatternAttr(markerPattern.bgcolor, 0, null);
358-
var patternSize = Math.min(12, Drawing.getPatternAttr(markerPattern.size, 0, 8));
359-
var patternSolidity = Drawing.getPatternAttr(markerPattern.solidity, 0, 0.3);
367+
var patternFGColor = Drawing.getPatternAttr(markerPattern.fgcolor, 0, null);
368+
var patternFGOpacity = markerPattern.fgopacity;
369+
var patternSize = dimAttr(markerPattern.size, 8, 10);
370+
var patternSolidity = dimAttr(markerPattern.solidity, 0.5, 1);
360371
var patternID = 'legend-' + trace.uid;
361-
p.call(Drawing.pattern, gd, patternID, patternShape, patternBGColor,
362-
fillColor, patternSize, patternSolidity, 'fill');
372+
p.call(
373+
Drawing.pattern, 'legend', gd, patternID,
374+
patternShape, patternSize, patternSolidity,
375+
mcc, markerPattern.fillmode,
376+
patternBGColor, patternFGColor, patternFGOpacity
377+
);
363378
} else {
364379
p.call(Color.fill, fillColor);
365380
}
@@ -662,3 +677,9 @@ function getStyleGuide(d) {
662677
anyFill: showFill || showGradientFill,
663678
};
664679
}
680+
681+
function dimAttr(v, dflt, max) {
682+
if(v && Lib.isArrayOrTypedArray(v)) return dflt;
683+
if(v > max) return max;
684+
return v;
685+
}

src/lib/coerce.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var tinycolor = require('tinycolor2');
55

66
var baseTraceAttrs = require('../plots/attributes');
77
var colorscales = require('../components/colorscale/scales');
8+
var Color = require('../components/color');
89
var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
910

1011
var nestedProperty = require('./nested_property');
@@ -427,12 +428,30 @@ exports.coerceFont = function(coerce, attr, dfltObj) {
427428
/*
428429
* Shortcut to coerce the pattern attributes
429430
*/
430-
exports.coercePattern = function(coerce, attr) {
431+
exports.coercePattern = function(coerce, attr, markerColor, hasMarkerColorscale) {
431432
var shape = coerce(attr + '.shape');
432433
if(shape) {
433-
coerce(attr + '.size');
434-
coerce(attr + '.bgcolor');
435434
coerce(attr + '.solidity');
435+
coerce(attr + '.size');
436+
var fillmode = coerce(attr + '.fillmode');
437+
var isOverlay = fillmode === 'overlay';
438+
439+
if(!hasMarkerColorscale) {
440+
var bgcolor = coerce(attr + '.bgcolor', isOverlay ?
441+
markerColor :
442+
undefined
443+
);
444+
445+
coerce(attr + '.fgcolor', isOverlay ?
446+
Color.contrast(bgcolor) :
447+
markerColor
448+
);
449+
}
450+
451+
coerce(attr + '.fgopacity', isOverlay ?
452+
0.5 :
453+
1
454+
);
436455
}
437456
};
438457

src/traces/bar/style_defaults.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ var colorscaleDefaults = require('../../components/colorscale/defaults');
66
var coercePattern = require('../../lib').coercePattern;
77

88
module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) {
9-
coerce('marker.color', defaultColor);
10-
11-
if(hasColorscale(traceIn, 'marker')) {
9+
var markerColor = coerce('marker.color', defaultColor);
10+
var hasMarkerColorscale = hasColorscale(traceIn, 'marker');
11+
if(hasMarkerColorscale) {
1212
colorscaleDefaults(
1313
traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}
1414
);
@@ -24,8 +24,7 @@ module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, default
2424

2525
coerce('marker.line.width');
2626
coerce('marker.opacity');
27-
coercePattern(coerce, 'marker.pattern');
28-
27+
coercePattern(coerce, 'marker.pattern', markerColor, hasMarkerColorscale);
2928
coerce('selected.marker.color');
3029
coerce('unselected.marker.color');
3130
};

tasks/test_mock.js

+2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ function notBlackListed(name) {
226226
'mathjax',
227227
'ohlc_first',
228228
'pattern_bars',
229+
'pattern_fgcolor_overlay_fillmode',
230+
'pattern_with_colorscale',
229231
'plot_types',
230232
'polar_blank',
231233
'polar_dates',

test/image/baselines/pattern_bars.png

422 Bytes
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)