diff --git a/draftlogs/7673_add.md b/draftlogs/7673_add.md new file mode 100644 index 00000000000..d76f9595d12 --- /dev/null +++ b/draftlogs/7673_add.md @@ -0,0 +1 @@ + - Add support for dashed marker lines in scatter plots [[#7673](https://github.com/plotly/plotly.js/pull/7673)] diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 38e8686d102..1eda8c31e7b 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -975,6 +975,9 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { } else { sel.style('stroke-width', (d.isBlank ? 0 : lineWidth) + 'px'); + const lineDash = d.mld || (markerLine || {}).dash; + if(lineDash) drawing.dashLine(sel, lineDash, lineWidth); + var markerGradient = marker.gradient; var gradientType = d.mgt; diff --git a/src/components/legend/style.js b/src/components/legend/style.js index 849271d3962..c62970cdc0d 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -221,6 +221,7 @@ module.exports = function style(s, gd, legend) { dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]); dEdit.mlc = boundVal('marker.line.color', pickFirst); dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5], CST_MARKER_LINE_WIDTH); + dEdit.mld = boundVal('marker.line.dash', pickFirst); tEdit.marker = { sizeref: 1, sizemin: 1, diff --git a/src/traces/scatter/arrays_to_calcdata.js b/src/traces/scatter/arrays_to_calcdata.js index efa5332498c..07eac057140 100644 --- a/src/traces/scatter/arrays_to_calcdata.js +++ b/src/traces/scatter/arrays_to_calcdata.js @@ -38,6 +38,7 @@ module.exports = function arraysToCalcdata(cd, trace) { if(marker.line) { Lib.mergeArray(markerLine.color, cd, 'mlc'); Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw'); + Lib.mergeArray(markerLine.dash, cd, 'mld'); } var markerGradient = marker.gradient; diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 18231a889b7..255496750a0 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -555,6 +555,9 @@ module.exports = { anim: true, description: 'Sets the width (in px) of the lines bounding the marker points.' }, + dash: extendFlat({}, dash, { + arrayOk: true + }), editType: 'calc' }, colorScaleAttrs('marker.line', { anim: true }) diff --git a/src/traces/scatter/marker_defaults.js b/src/traces/scatter/marker_defaults.js index c4358730534..66c2a8dc8fd 100644 --- a/src/traces/scatter/marker_defaults.js +++ b/src/traces/scatter/marker_defaults.js @@ -64,6 +64,7 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout } coerce('marker.line.width', isBubble ? 1 : 0); + coerce('marker.line.dash'); } if(isBubble) { diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js index 68caaade24a..ded94305d8a 100644 --- a/src/traces/scattercarpet/attributes.js +++ b/src/traces/scattercarpet/attributes.js @@ -97,6 +97,7 @@ module.exports = { line: extendFlat( { width: scatterMarkerLineAttrs.width, + dash: scatterMarkerLineAttrs.dash, editType: 'calc' }, colorScaleAttrs('marker.line') diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 0f8cede9834..f2fb2639cf8 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -139,7 +139,8 @@ module.exports = overrideAll( colorbar: scatterMarkerAttrs.colorbar, line: extendFlat( { - width: scatterMarkerLineAttrs.width + width: scatterMarkerLineAttrs.width, + dash: scatterMarkerLineAttrs.dash }, colorAttributes('marker.line') ), diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 5954d376f6f..c51c1abdfbb 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -126,6 +126,7 @@ module.exports = { line: extendFlat( { width: scatterMarkerLineAttrs.width, + dash: scatterMarkerLineAttrs.dash, editType: 'calc' }, colorScaleAttrs('marker.line') diff --git a/test/image/mocks/scatter_marker_line_dash.json b/test/image/mocks/scatter_marker_line_dash.json new file mode 100644 index 00000000000..e860c1cc396 --- /dev/null +++ b/test/image/mocks/scatter_marker_line_dash.json @@ -0,0 +1,82 @@ +{ + "data": [ + { + "type": "scatter", + "mode": "markers", + "x": [1, 2, 3, 4, 5, 6], + "y": [1, 1, 1, 1, 1, 1], + "marker": { + "size": 30, + "color": "white", + "line": { + "color": "black", + "width": 3, + "dash": ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] + } + }, + "name": "Array of dashes" + }, + { + "type": "scatter", + "mode": "markers", + "x": [1, 2, 3, 4, 5, 6], + "y": [2, 2, 2, 2, 2, 2], + "marker": { + "size": 30, + "color": "rgba(255,0,0,0.2)", + "line": { + "color": "red", + "width": 4, + "dash": "dash" + } + }, + "name": "Single dash" + }, + { + "type": "scatter", + "mode": "markers", + "x": [1, 2, 3, 4, 5, 6], + "y": [3, 3, 3, 3, 3, 3], + "marker": { + "size": 30, + "symbol": "square", + "color": "white", + "line": { + "color": "blue", + "width": 2, + "dash": "dot" + } + }, + "name": "Dot with squares" + }, + { + "type": "scatter", + "mode": "markers", + "x": [1, 2, 3, 4, 5, 6], + "y": [4, 4, 4, 4, 4, 4], + "marker": { + "size": 30, + "symbol": "circle-open", + "line": { + "color": "green", + "width": 2, + "dash": "dash" + } + }, + "name": "Open markers with dash" + } + ], + "layout": { + "title": { + "text": "Scatter Marker Line Dash Support" + }, + "xaxis": { + "range": [0, 7] + }, + "yaxis": { + "range": [0, 4] + }, + "width": 600, + "height": 400 + } +} diff --git a/test/jasmine/tests/scatter_marker_line_dash_test.js b/test/jasmine/tests/scatter_marker_line_dash_test.js new file mode 100644 index 00000000000..e9125c39c96 --- /dev/null +++ b/test/jasmine/tests/scatter_marker_line_dash_test.js @@ -0,0 +1,130 @@ +var Plotly = require('../../../lib/index'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); + +describe('Test scatter marker line dash:', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should support marker line dash', function(done) { + Plotly.newPlot(gd, [{ + mode: 'markers', + x: [1, 2, 3], + y: [1, 2, 3], + marker: { + size: 20, + line: { + color: 'red', + width: 2, + dash: 'dash' + } + } + }]).then(function() { + var markers = gd.querySelectorAll('.point'); + expect(markers.length).toBe(3); + + markers.forEach(function(node) { + // In plotly.js, dash is applied via stroke-dasharray + expect(node.style.strokeDasharray).not.toBe(''); + }); + }) + .then(done, done.fail); + }); + + it('should support array marker line dash', function(done) { + Plotly.newPlot(gd, [{ + mode: 'markers', + x: [1, 2, 3], + y: [1, 2, 3], + marker: { + size: 20, + line: { + color: 'red', + width: 2, + dash: ['solid', 'dot', 'dash'] + } + } + }]).then(function() { + var markers = gd.querySelectorAll('.point'); + expect(markers.length).toBe(3); + + // 'solid' should have no dasharray or 'none' (represented as empty string in node.style.strokeDasharray) + // 'dot' and 'dash' should have numerical dasharrays + expect(markers[0].style.strokeDasharray).toBe(''); + expect(markers[1].style.strokeDasharray).not.toBe(''); + expect(markers[2].style.strokeDasharray).not.toBe(''); + }) + .then(done, done.fail); + }); + + it('should show marker line dash in the legend', function(done) { + Plotly.newPlot(gd, [{ + mode: 'markers', + x: [1, 2, 3], + y: [1, 2, 3], + marker: { + line: { + color: 'red', + width: 2, + dash: 'dash' + } + } + }]).then(function() { + var legendPoints = gd.querySelectorAll('.legendpoints path.point'); + expect(legendPoints.length).toBe(1); + expect(legendPoints[0].style.strokeDasharray).not.toBe(''); + }) + .then(done, done.fail); + }); + + it('should update marker line dash via restyle', function(done) { + Plotly.newPlot(gd, [{ + mode: 'markers', + x: [1, 2, 3], + y: [1, 2, 3], + marker: { + line: { + color: 'red', + width: 2, + dash: 'solid' + } + } + }]).then(function() { + var markers = gd.querySelectorAll('.point'); + expect(markers[0].style.strokeDasharray).toBe(''); + + return Plotly.restyle(gd, {'marker.line.dash': 'dot'}); + }).then(function() { + var markers = gd.querySelectorAll('.point'); + expect(markers[0].style.strokeDasharray).not.toBe(''); + }) + .then(done, done.fail); + }); + it('should support marker line dash on open markers', function(done) { + Plotly.newPlot(gd, [{ + mode: 'markers', + x: [1, 2, 3], + y: [1, 2, 3], + marker: { + symbol: 'circle-open', + line: { + color: 'red', + width: 2, + dash: 'dash' + } + } + }]).then(function() { + var markers = gd.querySelectorAll('.point'); + expect(markers.length).toBe(3); + + markers.forEach(function(node) { + expect(node.style.strokeDasharray).not.toBe(''); + }); + }) + .then(done, done.fail); + });}); diff --git a/test/plot-schema.json b/test/plot-schema.json index 211da680a56..04e324e6977 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -60613,6 +60613,26 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": true, + "description": "Sets the dash style of lines. Set to a dash type string (*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*) or a dash length list in px (eg *5px,10px,2px,2px*).", + "dflt": "solid", + "editType": "style", + "valType": "string", + "values": [ + "solid", + "dot", + "dash", + "longdash", + "dashdot", + "longdashdot" + ] + }, + "dashsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `dash`.", + "editType": "none", + "valType": "string" + }, "editType": "calc", "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `marker.line.color` is set to a numerical array. If true, `marker.line.cmin` will correspond to the last color in the array and `marker.line.cmax` will correspond to the first color.", @@ -65817,6 +65837,26 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": true, + "description": "Sets the dash style of lines. Set to a dash type string (*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*) or a dash length list in px (eg *5px,10px,2px,2px*).", + "dflt": "solid", + "editType": "style", + "valType": "string", + "values": [ + "solid", + "dot", + "dash", + "longdash", + "dashdot", + "longdashdot" + ] + }, + "dashsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `dash`.", + "editType": "none", + "valType": "string" + }, "editType": "calc", "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `marker.line.color` is set to a numerical array. If true, `marker.line.cmin` will correspond to the last color in the array and `marker.line.cmax` will correspond to the first color.", @@ -68123,6 +68163,26 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": true, + "description": "Sets the dash style of lines. Set to a dash type string (*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*) or a dash length list in px (eg *5px,10px,2px,2px*).", + "dflt": "solid", + "editType": "calc", + "valType": "string", + "values": [ + "solid", + "dot", + "dash", + "longdash", + "dashdot", + "longdashdot" + ] + }, + "dashsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `dash`.", + "editType": "none", + "valType": "string" + }, "editType": "calc", "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `marker.line.color` is set to a numerical array. If true, `marker.line.cmin` will correspond to the last color in the array and `marker.line.cmax` will correspond to the first color.", @@ -75980,6 +76040,26 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": true, + "description": "Sets the dash style of lines. Set to a dash type string (*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*) or a dash length list in px (eg *5px,10px,2px,2px*).", + "dflt": "solid", + "editType": "style", + "valType": "string", + "values": [ + "solid", + "dot", + "dash", + "longdash", + "dashdot", + "longdashdot" + ] + }, + "dashsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `dash`.", + "editType": "none", + "valType": "string" + }, "editType": "calc", "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `marker.line.color` is set to a numerical array. If true, `marker.line.cmin` will correspond to the last color in the array and `marker.line.cmax` will correspond to the first color.", @@ -80482,6 +80562,26 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": true, + "description": "Sets the dash style of lines. Set to a dash type string (*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*) or a dash length list in px (eg *5px,10px,2px,2px*).", + "dflt": "solid", + "editType": "style", + "valType": "string", + "values": [ + "solid", + "dot", + "dash", + "longdash", + "dashdot", + "longdashdot" + ] + }, + "dashsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `dash`.", + "editType": "none", + "valType": "string" + }, "editType": "calc", "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `marker.line.color` is set to a numerical array. If true, `marker.line.cmin` will correspond to the last color in the array and `marker.line.cmax` will correspond to the first color.", @@ -82800,6 +82900,26 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": true, + "description": "Sets the dash style of lines. Set to a dash type string (*solid*, *dot*, *dash*, *longdash*, *dashdot*, or *longdashdot*) or a dash length list in px (eg *5px,10px,2px,2px*).", + "dflt": "solid", + "editType": "style", + "valType": "string", + "values": [ + "solid", + "dot", + "dash", + "longdash", + "dashdot", + "longdashdot" + ] + }, + "dashsrc": { + "description": "Sets the source reference on Chart Studio Cloud for `dash`.", + "editType": "none", + "valType": "string" + }, "editType": "calc", "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `marker.line.color` is set to a numerical array. If true, `marker.line.cmin` will correspond to the last color in the array and `marker.line.cmax` will correspond to the first color.",