diff --git a/draftlogs/7280_add.md b/draftlogs/7280_add.md new file mode 100644 index 00000000000..b4a3ecd69d7 --- /dev/null +++ b/draftlogs/7280_add.md @@ -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)] diff --git a/src/components/drawing/attributes.js b/src/components/drawing/attributes.js index 0d3447c620d..db725070456 100644 --- a/src/components/drawing/attributes.js +++ b/src/components/drawing/attributes.js @@ -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'], diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 5fa39480d6e..3cb3b447eeb 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -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, @@ -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 = [ @@ -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; @@ -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) || diff --git a/src/components/legend/style.js b/src/components/legend/style.js index f1400698d8b..849271d3962 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -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); diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 7e879b59992..9fa59255bc1 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -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'; diff --git a/src/plot_api/validate.js b/src/plot_api/validate.js index 45be7d7bbc4..8119eb4a627 100644 --- a/src/plot_api/validate.js +++ b/src/plot_api/validate.js @@ -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)) + ) ) { list.push(format('dynamic', base, p, valIn, valOut)); } diff --git a/test/image/baselines/zz-pattern_bars-path.png b/test/image/baselines/zz-pattern_bars-path.png new file mode 100644 index 00000000000..cff51494b5b Binary files /dev/null and b/test/image/baselines/zz-pattern_bars-path.png differ diff --git a/test/image/mocks/zz-pattern_bars-path.json b/test/image/mocks/zz-pattern_bars-path.json new file mode 100644 index 00000000000..13e554ed4c3 --- /dev/null +++ b/test/image/mocks/zz-pattern_bars-path.json @@ -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] + } + } +} diff --git a/test/plot-schema.json b/test/plot-schema.json index 6aa77cf3338..dc18446a37e 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -17808,6 +17808,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -19835,6 +19846,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -37562,6 +37584,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -41225,6 +41258,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -46456,6 +46500,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -56226,6 +56281,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -58612,6 +58678,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -87705,6 +87782,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true, @@ -92346,6 +92434,17 @@ "overlay" ] }, + "path": { + "arrayOk": true, + "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.", + "editType": "style", + "valType": "string" + }, + "pathsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `path`.", + "editType": "none", + "valType": "string" + }, "role": "object", "shape": { "arrayOk": true,