Skip to content
1 change: 1 addition & 0 deletions draftlogs/7280_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add `pattern.path` attribute as an alternative to the preset `pattern.shape` values, so you can use any SVG path string as a pattern fill. [[#7280](https://github.com/plotly/plotly.js/pull/7280)]
10 changes: 10 additions & 0 deletions src/components/drawing/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ exports.pattern = {
'By default, no pattern is used for filling the area.',
].join(' ')
},
path: {
valType: 'string',
arrayOk: true,
editType: 'style',
description: [
'Sets a custom path for pattern fill.',
'Use with no `shape` or `solidity`, provide an SVG path string for',
'the regions of the square from (0,0) to (`size`,`size`) to color.'
].join(' ')
},
fillmode: {
valType: 'enumerated',
values: ['replace', 'overlay'],
Expand Down
35 changes: 25 additions & 10 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,14 @@ drawing.dashStyle = function(dash, lineWidth) {
function setFillStyle(sel, trace, gd, forLegend) {
var markerPattern = trace.fillpattern;
var fillgradient = trace.fillgradient;
var patternShape = markerPattern && drawing.getPatternAttr(markerPattern.shape, 0, '');
var pAttr = drawing.getPatternAttr;
var patternShape = markerPattern && (pAttr(markerPattern.shape, 0, '') || pAttr(markerPattern.path, 0, ''));
if(patternShape) {
var patternBGColor = drawing.getPatternAttr(markerPattern.bgcolor, 0, null);
var patternFGColor = drawing.getPatternAttr(markerPattern.fgcolor, 0, null);
var patternBGColor = pAttr(markerPattern.bgcolor, 0, null);
var patternFGColor = pAttr(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 patternSize = pAttr(markerPattern.size, 0, 8);
var patternSolidity = pAttr(markerPattern.solidity, 0, 0.3);
var patternID = trace.uid;
drawing.pattern(sel, 'point', gd, patternID,
patternShape, patternSize, patternSolidity,
Expand Down Expand Up @@ -662,6 +663,16 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity,
fill: fgRGB
};
break;
default:
width = size;
height = size;
patternTag = 'path';
patternAttrs = {
d: shape,
opacity: opacity,
fill: fgRGB
};
break;
}

var str = [
Expand Down Expand Up @@ -869,7 +880,10 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) {
}

var markerPattern = marker.pattern;
var patternShape = markerPattern && drawing.getPatternAttr(markerPattern.shape, d.i, '');
var pAttr = drawing.getPatternAttr;
var patternShape = markerPattern && (
pAttr(markerPattern.shape, d.i, '') || pAttr(markerPattern.path, d.i, '')
);

if(gradientType && gradientType !== 'none') {
var gradientColor = d.mgc;
Expand All @@ -888,14 +902,15 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) {
fgcolor = pt.color;
perPointPattern = true;
}
var patternFGColor = drawing.getPatternAttr(fgcolor, d.i, (pt && pt.color) || null);
var patternFGColor = pAttr(fgcolor, d.i, (pt && pt.color) || null);

var patternBGColor = drawing.getPatternAttr(markerPattern.bgcolor, d.i, null);
var patternBGColor = pAttr(markerPattern.bgcolor, 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 patternSize = pAttr(markerPattern.size, d.i, 8);
var patternSolidity = pAttr(markerPattern.solidity, d.i, 0.3);
perPointPattern = perPointPattern || d.mcc ||
Lib.isArrayOrTypedArray(markerPattern.shape) ||
Lib.isArrayOrTypedArray(markerPattern.path) ||
Lib.isArrayOrTypedArray(markerPattern.bgcolor) ||
Lib.isArrayOrTypedArray(markerPattern.fgcolor) ||
Lib.isArrayOrTypedArray(markerPattern.size) ||
Expand Down
9 changes: 6 additions & 3 deletions src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,14 @@ module.exports = function style(s, gd, legend) {
var fillColor = mcc || d0.mc || marker.color;

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

if(patternShape) {
var patternBGColor = Drawing.getPatternAttr(markerPattern.bgcolor, 0, null);
var patternFGColor = Drawing.getPatternAttr(markerPattern.fgcolor, 0, null);
var patternBGColor = pAttr(markerPattern.bgcolor, 0, null);
var patternFGColor = pAttr(markerPattern.fgcolor, 0, null);
var patternFGOpacity = markerPattern.fgopacity;
var patternSize = dimAttr(markerPattern.size, 8, 10);
var patternSolidity = dimAttr(markerPattern.solidity, 0.5, 1);
Expand Down
10 changes: 8 additions & 2 deletions src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,14 @@ exports.coerceFont = function(coerce, attr, dfltObj, opts) {
*/
exports.coercePattern = function(coerce, attr, markerColor, hasMarkerColorscale) {
var shape = coerce(attr + '.shape');
if(shape) {
coerce(attr + '.solidity');
var path;
if(!shape) {
path = coerce(attr + '.path');
}
if(shape || path) {
if(shape) {
coerce(attr + '.solidity');
}
coerce(attr + '.size');
var fillmode = coerce(attr + '.fillmode');
var isOverlay = fillmode === 'overlay';
Expand Down
6 changes: 5 additions & 1 deletion src/plot_api/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ function crawl(objIn, objOut, schema, list, base, path) {
} else if(!Lib.validate(valIn, nestedSchema)) {
list.push(format('value', base, p, valIn));
} else if(nestedSchema.valType === 'enumerated' &&
((nestedSchema.coerceNumber && valIn !== +valOut) || valIn !== valOut)
(
(nestedSchema.coerceNumber && valIn !== +valOut) ||
(!isArrayOrTypedArray(valIn) && valIn !== valOut) ||
(String(valIn) !== String(valOut))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we're comparing the original data array to something we coerced off a deep copy, arrays that make it here will never be ===

)
) {
list.push(format('dynamic', base, p, valIn, valOut));
}
Expand Down
Binary file added test/image/baselines/zz-pattern_bars-path.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
218 changes: 218 additions & 0 deletions test/image/mocks/zz-pattern_bars-path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
{
"data": [
{
"x": ["a", "b", "c", "d", "e"],
"y": [1, 2, 3, 4, 5],
"name": "Bar 1",
"type": "bar",
"textposition": "outside",
"text": "bgcolor",
"marker": {
"pattern": {
"shape": "/",
"bgcolor": ["", "lightblue", "blue", "darkblue", "black"]
}
}
},
{
"x": ["a", "b", "c", "d", "e"],
"y": [2, 3, 4, 5, 6],
"name": "Bar 2",
"type": "bar",
"textposition": "outside",
"text": "shape",
"marker": {
"pattern": {
"shape": ["|", "/", "-", "\\", "|"]
}
}
},
{
"x": ["a", "b", "c", "d", "e"],
"y": [3, 4, 5, 6, 7],
"name": "Bar 3",
"type": "bar",
"textposition": "outside",
"text": "size",
"marker": {
"pattern": {
"shape": "x",
"size": [4, 6, 8, 10, 12]
}
}
},
{
"x": ["a", "b", "c", "d", "e"],
"y": [6, 7, 8, 9, 10],
"name": "Bar 4",
"type": "bar",
"textposition": "outside",
"text": "solidity",
"marker": {
"pattern": {
"shape": ".",
"bgcolor": "yellow",
"solidity": [0.1, 0.3, 0.5, 0.7, 0.9]
}
}
},
{
"x": ["a", "b", "c", "d", "e"],
"y": [7, 8, 9, 10, 11],
"name": "Bar 5",
"type": "bar",
"textposition": "outside",
"text": "path",
"marker": {
"pattern": {
"path": [
"M0,0H4V4H0Z",
"M0,0H6V6Z",
"M0,0V4H4Z",
"M0,0C0,2,4,2,4,4C4,6,0,6,0,8H2C2,6,6,6,6,4C6,2,2,2,2,0Z",
"M4,4L7,2A3.5,3.5,0,1,0,7,6Z"
],
"fgcolor": "yellow",
"bgcolor": "black"
}
}
},
{
"r": [1, 2, 3, 4],
"type": "barpolar",
"name": "Barpolar 1",
"marker": {
"color": "red",
"pattern": {
"shape": "+",
"size": [1, 2, 3, 4]
}
}
},
{
"r": [2, 3, 4, 1],
"type": "barpolar",
"name": "Barpolar 2",
"marker": {
"color": "rgba(0,127,0,0.5)",
"pattern": {
"shape": "x",
"solidity": 0.75
}
}
},
{
"r": [3, 4, 1, 2],
"type": "barpolar",
"name": "Barpolar 3",
"marker": {
"color": "blue",
"pattern": {
"shape": ["|", "-", "|", "-"],
"solidity": 0.5
}
}
},
{
"r": [4, 1, 2, 3],
"type": "barpolar",
"name": "Barpolar 4",
"marker": {
"color": "orange",
"pattern": {
"shape": ".",
"bgcolor": "yellow",
"solidity": [0.2, 0.8, 0.6, 0.4]
}
}
},

{
"xaxis": "x2",
"yaxis": "y2",
"y": ["A", "A", "A", "A", "B", "B", "C"],
"name": "Histogram 1",
"type": "histogram",
"marker": {
"color": "yellow",
"line": {
"color": "black",
"width": 2
},
"pattern": {
"bgcolor": "blue",
"shape": "."
}
}
},
{
"xaxis": "x2",
"yaxis": "y2",
"y": ["C", "C", "C", "C", "B", "B", "A"],
"name": "Histogram 2",
"type": "histogram",
"marker": {
"color": "yellow",
"line": {
"color": "red",
"width": 4
},
"pattern": {
"bgcolor": "rgba(255, 127,0,0.5)",
"shape": "x"
}
}
},

{
"xaxis": "x3",
"yaxis": "y3",
"x": [3, 2, 1],
"y": ["U", "V", "W"],
"name": "Funnel",
"type": "funnel"
}
],
"layout": {
"title": {
"text": "pattern options"
},
"width": 1000,
"height": 600,

"xaxis": {
"domain": [0, 1]
},
"yaxis": {
"range": [0, 11],
"domain": [0, 0.475]
},

"polar": {
"domain": {
"x": [0.35, 0.65],
"y": [0.525, 1]
}
},

"xaxis2": {
"anchor": "y2",
"gridcolor": "black",
"gridwidth": 2,
"domain": [0, 0.3]
},
"yaxis2": {
"anchor": "x2",
"domain": [0.525, 1]
},

"xaxis3": {
"anchor": "y3",
"domain": [0.7, 1]
},
"yaxis3": {
"anchor": "x3",
"domain": [0.525, 1]
}
}
}
Loading