From de99bdc2fc44d293e4726c12363c444fbac39f7b Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sat, 13 Dec 2025 08:37:28 +1100 Subject: [PATCH 01/26] Add support for dashed marker lines in scatter plots - Enable `marker.line.dash` attribute in scatter trace definitions with support for array values - Apply dash patterns to marker outlines in both open and closed marker styles - Merge dash data into calcdata for per-point dash styling - Include dash information in legend marker rendering to match trace appearance - Default dash handling falls back to trace-level `marker.line.dash` when per-point values are not specified --- src/components/drawing/index.js | 4 ++++ src/components/legend/style.js | 1 + src/traces/scatter/arrays_to_calcdata.js | 1 + src/traces/scatter/attributes.js | 4 ++++ src/traces/scatter/marker_defaults.js | 1 + 5 files changed, 11 insertions(+) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 38e8686d102..198defe0cd1 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -965,6 +965,8 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { } } + var lineDash = d.mld || (markerLine || {}).dash; + if (d.om) { // open markers can't have zero linewidth, default to 1px, // and use fill color as stroke color @@ -972,6 +974,7 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { 'stroke-width': (lineWidth || 1) + 'px', fill: 'none' }); + drawing.dashLine(sel, lineDash, lineWidth || 1); } else { sel.style('stroke-width', (d.isBlank ? 0 : lineWidth) + 'px'); @@ -1057,6 +1060,7 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { if (lineWidth) { Color.stroke(sel, lineColor); + drawing.dashLine(sel, lineDash, lineWidth); } } }; 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..c137543d612 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -555,6 +555,10 @@ module.exports = { anim: true, description: 'Sets the width (in px) of the lines bounding the marker points.' }, + dash: extendFlat({}, dash, { + arrayOk: true, + editType: 'style' + }), 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) { From 8c1fdf2e0f11ae53aea903daccb2530cd52f4682 Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sat, 13 Dec 2025 09:03:17 +1100 Subject: [PATCH 02/26] Add changelog entry for dashed marker line support - Document new feature that adds support for dashed marker lines in scatter plots - Reference pull request #7673 for tracking purposes --- draftlogs/7673_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7673_add.md 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)] From d838891cdcb03dccda7989bea27506345285f370 Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sat, 13 Dec 2025 10:26:21 +1100 Subject: [PATCH 03/26] Add dash attribute support to marker lines across scatter trace types - Extends marker line styling capabilities by including the `dash` attribute alongside existing `width` attribute - Enables dashed marker line borders for `scatter3d`, `scattercarpet`, `scattergeo`, `scattergl`, `scatterternary`, and `splom` trace types - Maintains consistency with base scatter trace marker line attributes by reusing `scatterMarkerLineAttrs.dash` - Applies appropriate constraints where needed, such as `arrayOk: false` for 3D traces and `editType: 'calc'` for other trace types --- src/traces/scatter3d/attributes.js | 3 ++- src/traces/scattercarpet/attributes.js | 1 + src/traces/scattergeo/attributes.js | 3 ++- src/traces/scattergl/attributes.js | 3 ++- src/traces/scatterternary/attributes.js | 1 + src/traces/splom/attributes.js | 1 + 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index 17023178d28..cabbfa38c9c 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -150,7 +150,8 @@ var attrs = (module.exports = overrideAll( line: extendFlat( { - width: extendFlat({}, scatterMarkerLineAttrs.width, { arrayOk: false }) + width: extendFlat({}, scatterMarkerLineAttrs.width, { arrayOk: false }), + dash: extendFlat({}, scatterMarkerLineAttrs.dash, { arrayOk: false }) }, colorAttributes('marker.line') ) 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/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 8f05e9f5cc4..28203aea7ae 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -83,7 +83,8 @@ var attrs = (module.exports = overrideAll( opacity: scatterMarkerAttrs.opacity, colorbar: scatterMarkerAttrs.colorbar, line: extendFlat({}, colorScaleAttrs('marker.line'), { - width: scatterMarkerLineAttrs.width + width: scatterMarkerLineAttrs.width, + dash: scatterMarkerLineAttrs.dash }) }), connectgaps: scatterAttrs.connectgaps, 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/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index 679337b30bd..70b422a4a7f 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -14,6 +14,7 @@ var scatterMarkerLineAttrs = scatterMarkerAttrs.line; var markerLineAttrs = extendFlat(colorScaleAttrs('marker.line', { editTypeOverride: 'calc' }), { width: extendFlat({}, scatterMarkerLineAttrs.width, { editType: 'calc' }), + dash: extendFlat({}, scatterMarkerLineAttrs.dash, { editType: 'calc' }), editType: 'calc' }); From 0df71bb71f4f14c8cd0471e28a03cbddbf814693 Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sat, 13 Dec 2025 12:43:47 +1100 Subject: [PATCH 04/26] Hide marker line for blank points in scatter plots - Prevent marker line borders from rendering on blank data points by setting line width to 0 when `d.isBlank` is true in the `singlePointStyle()` function - Add `dash` and `dashsrc` schema attributes for marker line styling across multiple chart types to support customisable dash patterns - Ensure blank points appear correctly without visible outlines while maintaining proper styling for non-blank markers --- src/components/drawing/index.js | 2 +- test/plot-schema.json | 295 ++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 1 deletion(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 198defe0cd1..d132167d217 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -1060,7 +1060,7 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { if (lineWidth) { Color.stroke(sel, lineColor); - drawing.dashLine(sel, lineDash, lineWidth); + drawing.dashLine(sel, lineDash, d.isBlank ? 0 : lineWidth); } } }; diff --git a/test/plot-schema.json b/test/plot-schema.json index 211da680a56..fa0cb3c9466 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -17891,6 +17891,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.", @@ -19949,6 +19969,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.", @@ -36539,6 +36579,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.", @@ -41566,6 +41626,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.", @@ -60613,6 +60693,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.", @@ -63938,6 +64038,21 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": false, + "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" + ] + }, "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.", @@ -65716,6 +65831,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", "gradient": { "color": { @@ -65817,6 +65952,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 +68278,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.", @@ -70511,6 +70686,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.", @@ -75980,6 +76175,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.", @@ -78226,6 +78441,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 +80717,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 +83055,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.", @@ -85016,6 +85291,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.", From 26eecd969b46a0999767f2388c38cff6fc765750 Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sat, 13 Dec 2025 13:16:51 +1100 Subject: [PATCH 05/26] Add dash style property to marker line schemas - Add `dash` property to marker line configurations across multiple plot types, allowing customisation of line dash patterns - Support both predefined dash styles (`solid`, `dot`, `dash`, `longdash`, `dashdot`, `longdashdot`) and custom dash length lists in pixels - Include `dashsrc` property for Chart Studio Cloud integration to enable data source references for dash styling - Set default value to `solid` for consistent backward compatibility - Enable array support for most implementations to allow per-marker dash customisation --- dist/plot-schema.json | 295 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) diff --git a/dist/plot-schema.json b/dist/plot-schema.json index 211da680a56..fa0cb3c9466 100644 --- a/dist/plot-schema.json +++ b/dist/plot-schema.json @@ -17891,6 +17891,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.", @@ -19949,6 +19969,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.", @@ -36539,6 +36579,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.", @@ -41566,6 +41626,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.", @@ -60613,6 +60693,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.", @@ -63938,6 +64038,21 @@ "editType": "none", "valType": "string" }, + "dash": { + "arrayOk": false, + "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" + ] + }, "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.", @@ -65716,6 +65831,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", "gradient": { "color": { @@ -65817,6 +65952,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 +68278,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.", @@ -70511,6 +70686,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.", @@ -75980,6 +76175,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.", @@ -78226,6 +78441,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 +80717,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 +83055,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.", @@ -85016,6 +85291,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.", From 6ce3af49d967d49da814fcac53bc6fcdfb0c4c7d Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sat, 13 Dec 2025 13:42:24 +1100 Subject: [PATCH 06/26] Fix failing CI for `text_on_shapes_basic` pixel comparison - Simplify the dash line styling implementation by replacing the single `.style()` call with an object parameter with chained `.style()` method calls - Set `stroke-dasharray` to `null` when dash is falsy to properly clear the style rather than setting it to an empty or undefined value - Improve code readability through method chaining pattern --- src/components/drawing/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index d132167d217..9280ceb1aa6 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -193,10 +193,8 @@ drawing.dashLine = function (s, dash, lineWidth) { dash = drawing.dashStyle(dash, lineWidth); - s.style({ - 'stroke-dasharray': dash, - 'stroke-width': lineWidth + 'px' - }); + s.style('stroke-width', lineWidth + 'px') + .style('stroke-dasharray', dash || null); }; drawing.dashStyle = function (dash, lineWidth) { From 753c6ed0f07697ef7f24dac89d9846257e1ddad5 Mon Sep 17 00:00:00 2001 From: chrimaho Date: Sun, 14 Dec 2025 08:52:40 +1100 Subject: [PATCH 07/26] Simplify stroke styling and remove unused dash line logic - Consolidate style application in `dashLine()` function to use object notation instead of chained method calls - Remove unused `lineDash` variable and associated `dashLine()` calls in marker styling logic - Eliminate redundant dash line styling for open markers and blank points that was not being applied correctly --- src/components/drawing/index.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 9280ceb1aa6..38e8686d102 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -193,8 +193,10 @@ drawing.dashLine = function (s, dash, lineWidth) { dash = drawing.dashStyle(dash, lineWidth); - s.style('stroke-width', lineWidth + 'px') - .style('stroke-dasharray', dash || null); + s.style({ + 'stroke-dasharray': dash, + 'stroke-width': lineWidth + 'px' + }); }; drawing.dashStyle = function (dash, lineWidth) { @@ -963,8 +965,6 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { } } - var lineDash = d.mld || (markerLine || {}).dash; - if (d.om) { // open markers can't have zero linewidth, default to 1px, // and use fill color as stroke color @@ -972,7 +972,6 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { 'stroke-width': (lineWidth || 1) + 'px', fill: 'none' }); - drawing.dashLine(sel, lineDash, lineWidth || 1); } else { sel.style('stroke-width', (d.isBlank ? 0 : lineWidth) + 'px'); @@ -1058,7 +1057,6 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { if (lineWidth) { Color.stroke(sel, lineColor); - drawing.dashLine(sel, lineDash, d.isBlank ? 0 : lineWidth); } } }; From 6de355d47a5b0634f54e399379d410936c8ece1d Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:19:17 +0000 Subject: [PATCH 08/26] update plot-schema diff --- test/plot-schema.json | 108 ++---------------------------------------- 1 file changed, 4 insertions(+), 104 deletions(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index fa0cb3c9466..0fc92fa7ea7 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -17891,26 +17891,6 @@ "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.", @@ -19969,26 +19949,6 @@ "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.", @@ -36579,26 +36539,6 @@ "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.", @@ -41626,26 +41566,6 @@ "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.", @@ -65831,26 +65751,6 @@ "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", "gradient": { "color": { @@ -68282,7 +68182,7 @@ "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", + "editType": "calc", "valType": "string", "values": [ "solid", @@ -70690,7 +70590,7 @@ "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", + "editType": "calc", "valType": "string", "values": [ "solid", @@ -78445,7 +78345,7 @@ "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", + "editType": "calc", "valType": "string", "values": [ "solid", @@ -85295,7 +85195,7 @@ "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", + "editType": "calc", "valType": "string", "values": [ "solid", From 85730675c258d1bc2923685a9ff509de1a1e552c Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:30:38 +1100 Subject: [PATCH 09/26] Revert `plot-schema` to version from `master` branch --- dist/plot-schema.json | 295 ------------------------------------------ test/plot-schema.json | 195 ---------------------------- 2 files changed, 490 deletions(-) diff --git a/dist/plot-schema.json b/dist/plot-schema.json index fa0cb3c9466..211da680a56 100644 --- a/dist/plot-schema.json +++ b/dist/plot-schema.json @@ -17891,26 +17891,6 @@ "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.", @@ -19969,26 +19949,6 @@ "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.", @@ -36579,26 +36539,6 @@ "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.", @@ -41626,26 +41566,6 @@ "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.", @@ -60693,26 +60613,6 @@ "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.", @@ -64038,21 +63938,6 @@ "editType": "none", "valType": "string" }, - "dash": { - "arrayOk": false, - "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" - ] - }, "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.", @@ -65831,26 +65716,6 @@ "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", "gradient": { "color": { @@ -65952,26 +65817,6 @@ "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.", @@ -68278,26 +68123,6 @@ "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.", @@ -70686,26 +70511,6 @@ "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.", @@ -76175,26 +75980,6 @@ "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.", @@ -78441,26 +78226,6 @@ "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.", @@ -80717,26 +80482,6 @@ "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.", @@ -83055,26 +82800,6 @@ "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.", @@ -85291,26 +85016,6 @@ "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.", diff --git a/test/plot-schema.json b/test/plot-schema.json index 0fc92fa7ea7..211da680a56 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -60613,26 +60613,6 @@ "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.", @@ -63958,21 +63938,6 @@ "editType": "none", "valType": "string" }, - "dash": { - "arrayOk": false, - "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" - ] - }, "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.", @@ -65852,26 +65817,6 @@ "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.", @@ -68178,26 +68123,6 @@ "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.", @@ -70586,26 +70511,6 @@ "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.", @@ -76075,26 +75980,6 @@ "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.", @@ -78341,26 +78226,6 @@ "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.", @@ -80617,26 +80482,6 @@ "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.", @@ -82955,26 +82800,6 @@ "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.", @@ -85191,26 +85016,6 @@ "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.", From 6fa75bcfd21be55471534d6e8c9cdba2607a4113 Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:34:44 +1100 Subject: [PATCH 10/26] Remove `arrayOk` attribute from the `marker.line` attribute of the `scatter` plot --- src/traces/scatter/attributes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index c137543d612..f20980c07e3 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -556,7 +556,6 @@ module.exports = { description: 'Sets the width (in px) of the lines bounding the marker points.' }, dash: extendFlat({}, dash, { - arrayOk: true, editType: 'style' }), editType: 'calc' From ac01dc370be106ac1a7de6363c8afbdae16941f2 Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:41:48 +1100 Subject: [PATCH 11/26] Remove `marker.line.dash` changes from the `scatter3d`, `scattergl`, & `splom` traces. Revert to the changes from the `master` branch --- src/traces/scatter3d/attributes.js | 5 ++--- src/traces/scattergl/attributes.js | 5 ++--- src/traces/splom/attributes.js | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index cabbfa38c9c..6e49727e2d3 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -150,8 +150,7 @@ var attrs = (module.exports = overrideAll( line: extendFlat( { - width: extendFlat({}, scatterMarkerLineAttrs.width, { arrayOk: false }), - dash: extendFlat({}, scatterMarkerLineAttrs.dash, { arrayOk: false }) + width: extendFlat({}, scatterMarkerLineAttrs.width, { arrayOk: false }) }, colorAttributes('marker.line') ) @@ -179,4 +178,4 @@ var attrs = (module.exports = overrideAll( 'nested' )); -attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; +attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; \ No newline at end of file diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 28203aea7ae..95714f96d58 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -83,8 +83,7 @@ var attrs = (module.exports = overrideAll( opacity: scatterMarkerAttrs.opacity, colorbar: scatterMarkerAttrs.colorbar, line: extendFlat({}, colorScaleAttrs('marker.line'), { - width: scatterMarkerLineAttrs.width, - dash: scatterMarkerLineAttrs.dash + width: scatterMarkerLineAttrs.width }) }), connectgaps: scatterAttrs.connectgaps, @@ -112,4 +111,4 @@ attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'c attrs.hovertemplate = scatterAttrs.hovertemplate; attrs.hovertemplatefallback = scatterAttrs.hovertemplatefallback; attrs.texttemplate = scatterAttrs.texttemplate; -attrs.texttemplatefallback = scatterAttrs.texttemplatefallback; +attrs.texttemplatefallback = scatterAttrs.texttemplatefallback; \ No newline at end of file diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index 70b422a4a7f..7cf2cd94090 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -14,7 +14,6 @@ var scatterMarkerLineAttrs = scatterMarkerAttrs.line; var markerLineAttrs = extendFlat(colorScaleAttrs('marker.line', { editTypeOverride: 'calc' }), { width: extendFlat({}, scatterMarkerLineAttrs.width, { editType: 'calc' }), - dash: extendFlat({}, scatterMarkerLineAttrs.dash, { editType: 'calc' }), editType: 'calc' }); @@ -184,4 +183,4 @@ module.exports = { }, opacity: scatterGlAttrs.opacity -}; +}; \ No newline at end of file From f348454ee7421c2c339e8d6ec334df8a484f7594 Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:54:29 +1100 Subject: [PATCH 12/26] Implement logic to actually draw the dashed lines --- src/components/drawing/index.js | 3 +++ 1 file changed, 3 insertions(+) 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; From f7335055dd71b0a41244ff0f223f2ba2a31fd65d Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Mon, 19 Jan 2026 07:55:10 +1100 Subject: [PATCH 13/26] Fix missing end of file newline - In the `attributes.js` file for the traces `scatter3d`, `scattergl`, and `splom` --- src/traces/scatter3d/attributes.js | 2 +- src/traces/scattergl/attributes.js | 2 +- src/traces/splom/attributes.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index 6e49727e2d3..17023178d28 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -178,4 +178,4 @@ var attrs = (module.exports = overrideAll( 'nested' )); -attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; \ No newline at end of file +attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 95714f96d58..8f05e9f5cc4 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -111,4 +111,4 @@ attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'c attrs.hovertemplate = scatterAttrs.hovertemplate; attrs.hovertemplatefallback = scatterAttrs.hovertemplatefallback; attrs.texttemplate = scatterAttrs.texttemplate; -attrs.texttemplatefallback = scatterAttrs.texttemplatefallback; \ No newline at end of file +attrs.texttemplatefallback = scatterAttrs.texttemplatefallback; diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index 7cf2cd94090..679337b30bd 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -183,4 +183,4 @@ module.exports = { }, opacity: scatterGlAttrs.opacity -}; \ No newline at end of file +}; From 3dd16f77b87db74e39fa45b6d06b22d6df42790f Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Mon, 19 Jan 2026 07:58:44 +1100 Subject: [PATCH 14/26] Add `arrayOk` attribute to `marker.line.dash` for scatter plots. - Enable array support for the `dash` attribute. - Correct line from `editType: 'style'` to `arrayOk: true` --- src/traces/scatter/attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index f20980c07e3..255496750a0 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -556,7 +556,7 @@ module.exports = { description: 'Sets the width (in px) of the lines bounding the marker points.' }, dash: extendFlat({}, dash, { - editType: 'style' + arrayOk: true }), editType: 'calc' }, From 7eb1943296ac6ca16a889907980f2f5022390753 Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Mon, 19 Jan 2026 08:40:25 +1100 Subject: [PATCH 15/26] Add tests and mock data for scatter marker line dash support - Introduce a new mock data file for testing scatter marker line dash - Implement tests for marker line dash functionality - Validate support for array of dashes in markers - Ensure marker line dash is displayed in the legend - Confirm marker line dash updates via restyle - Test marker line dash on open markers --- .../image/mocks/scatter_marker_line_dash.json | 82 +++++++++++ .../tests/scatter_marker_line_dash_test.js | 130 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 test/image/mocks/scatter_marker_line_dash.json create mode 100644 test/jasmine/tests/scatter_marker_line_dash_test.js 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); + });}); From 90ab10586a95da2bbb21bb7d701b23c2e0ba7f79 Mon Sep 17 00:00:00 2001 From: chrimaho <44449504+chrimaho@users.noreply.github.com> Date: Mon, 19 Jan 2026 08:42:38 +1100 Subject: [PATCH 16/26] Update `plot-schema` diff --- test/plot-schema.json | 120 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) 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.", From e8a702dc5c40403f50664ac07daa0b0d04889817 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Thu, 29 Jan 2026 13:54:08 -0700 Subject: [PATCH 17/26] Rename test file --- ...ter_marker_line_dash.json => zz-scatter_marker_line_dash.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/image/mocks/{scatter_marker_line_dash.json => zz-scatter_marker_line_dash.json} (100%) diff --git a/test/image/mocks/scatter_marker_line_dash.json b/test/image/mocks/zz-scatter_marker_line_dash.json similarity index 100% rename from test/image/mocks/scatter_marker_line_dash.json rename to test/image/mocks/zz-scatter_marker_line_dash.json From cddc4654f14d5f8a8003a5ce3ca7bf754cc404ab Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Thu, 29 Jan 2026 13:54:44 -0700 Subject: [PATCH 18/26] Update mock layout --- test/image/mocks/zz-scatter_marker_line_dash.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/image/mocks/zz-scatter_marker_line_dash.json b/test/image/mocks/zz-scatter_marker_line_dash.json index e860c1cc396..e5b7c56f356 100644 --- a/test/image/mocks/zz-scatter_marker_line_dash.json +++ b/test/image/mocks/zz-scatter_marker_line_dash.json @@ -74,7 +74,7 @@ "range": [0, 7] }, "yaxis": { - "range": [0, 4] + "range": [0, 5] }, "width": 600, "height": 400 From 67458e7c2a230e6dc2bb14b602395a1e69e3d0e9 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Thu, 29 Jan 2026 18:52:55 -0700 Subject: [PATCH 19/26] Linting/formatting --- src/traces/scatter/marker_defaults.js | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/traces/scatter/marker_defaults.js b/src/traces/scatter/marker_defaults.js index 66c2a8dc8fd..c78aff05506 100644 --- a/src/traces/scatter/marker_defaults.js +++ b/src/traces/scatter/marker_defaults.js @@ -20,62 +20,62 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout opts = opts || {}; // marker.color inherit from line.color (even if line.color is an array) - if(lineColor) defaultColor = lineColor; + if (lineColor) defaultColor = lineColor; coerce('marker.symbol'); coerce('marker.opacity', isBubble ? 0.7 : 1); coerce('marker.size'); - if(!opts.noAngle) { + if (!opts.noAngle) { coerce('marker.angle'); - if(!opts.noAngleRef) { + if (!opts.noAngleRef) { coerce('marker.angleref'); } - if(!opts.noStandOff) { + if (!opts.noStandOff) { coerce('marker.standoff'); } } coerce('marker.color', defaultColor); - if(hasColorscale(traceIn, 'marker')) { - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}); + if (hasColorscale(traceIn, 'marker')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: 'marker.', cLetter: 'c' }); } - if(!opts.noSelect) { + if (!opts.noSelect) { coerce('selected.marker.color'); coerce('unselected.marker.color'); coerce('selected.marker.size'); coerce('unselected.marker.size'); } - if(!opts.noLine) { + if (!opts.noLine) { // if there's a line with a different color than the marker, use // that line color as the default marker line color // (except when it's an array) // mostly this is for transparent markers to behave nicely - if(lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) { + if (lineColor && !Array.isArray(lineColor) && traceOut.marker.color !== lineColor) { defaultMLC = lineColor; - } else if(isBubble) defaultMLC = Color.background; + } else if (isBubble) defaultMLC = Color.background; else defaultMLC = Color.defaultLine; coerce('marker.line.color', defaultMLC); - if(hasColorscale(traceIn, 'marker.line')) { - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}); + if (hasColorscale(traceIn, 'marker.line')) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: 'marker.line.', cLetter: 'c' }); } coerce('marker.line.width', isBubble ? 1 : 0); coerce('marker.line.dash'); } - if(isBubble) { + if (isBubble) { coerce('marker.sizeref'); coerce('marker.sizemin'); coerce('marker.sizemode'); } - if(opts.gradient) { + if (opts.gradient) { var gradientType = coerce('marker.gradient.type'); - if(gradientType !== 'none') { + if (gradientType !== 'none') { coerce('marker.gradient.color'); } } From 65a0d87923f6da261354d15508f4df460b13579b Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Thu, 29 Jan 2026 18:55:02 -0700 Subject: [PATCH 20/26] Don't coerce marker.line.dash for some traces --- src/traces/scatter/marker_defaults.js | 26 ++++++++++---------------- src/traces/scatter3d/defaults.js | 6 +++++- src/traces/scattergl/defaults.js | 6 +++++- src/traces/scatterpolargl/defaults.js | 6 +++++- src/traces/splom/defaults.js | 6 +++++- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/traces/scatter/marker_defaults.js b/src/traces/scatter/marker_defaults.js index c78aff05506..b7edca3e6dd 100644 --- a/src/traces/scatter/marker_defaults.js +++ b/src/traces/scatter/marker_defaults.js @@ -12,13 +12,11 @@ var subTypes = require('./subtypes'); * gradient: caller supports gradients * noSelect: caller does not support selected/unselected attribute containers */ -module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) { +module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts = {}) { var isBubble = subTypes.isBubble(traceIn); var lineColor = (traceIn.line || {}).color; var defaultMLC; - opts = opts || {}; - // marker.color inherit from line.color (even if line.color is an array) if (lineColor) defaultColor = lineColor; @@ -27,13 +25,8 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout coerce('marker.size'); if (!opts.noAngle) { coerce('marker.angle'); - if (!opts.noAngleRef) { - coerce('marker.angleref'); - } - - if (!opts.noStandOff) { - coerce('marker.standoff'); - } + if (!opts.noAngleRef) coerce('marker.angleref'); + if (!opts.noStandOff) coerce('marker.standoff'); } coerce('marker.color', defaultColor); @@ -55,8 +48,11 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout // mostly this is for transparent markers to behave nicely if (lineColor && !Array.isArray(lineColor) && traceOut.marker.color !== lineColor) { defaultMLC = lineColor; - } else if (isBubble) defaultMLC = Color.background; - else defaultMLC = Color.defaultLine; + } else if (isBubble) { + defaultMLC = Color.background; + } else { + defaultMLC = Color.defaultLine; + } coerce('marker.line.color', defaultMLC); if (hasColorscale(traceIn, 'marker.line')) { @@ -64,7 +60,7 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout } coerce('marker.line.width', isBubble ? 1 : 0); - coerce('marker.line.dash'); + if (!opts.noLineDash) coerce('marker.line.dash'); } if (isBubble) { @@ -75,8 +71,6 @@ module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout if (opts.gradient) { var gradientType = coerce('marker.gradient.type'); - if (gradientType !== 'none') { - coerce('marker.gradient.color'); - } + if (gradientType !== 'none') coerce('marker.gradient.color'); } }; diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index e99182fef69..eb366090612 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -32,7 +32,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode'); if (subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noSelect: true, noAngle: true }); + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { + noAngle: true, + noLineDash: true, + noSelect: true + }); } if (subTypes.hasLines(traceOut)) { diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index efaecbb5e63..47f1dbd7e10 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -41,7 +41,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode', defaultMode); if (subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { + noAngleRef: true, + noLineDash: true, + noStandOff: true + }); coerce('marker.line.width', isOpen || isBubble ? 1 : 0); } diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index 7f095c4ea71..c540adc2acd 100644 --- a/src/traces/scatterpolargl/defaults.js +++ b/src/traces/scatterpolargl/defaults.js @@ -33,7 +33,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } if (subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { + noAngleRef: true, + noLineDash: true, + noStandOff: true + }); } if (subTypes.hasLines(traceOut)) { diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index d3c4154b9ba..a7ee35aedbe 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -37,7 +37,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('xhoverformat'); coerce('yhoverformat'); - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { + noAngleRef: true, + noLineDash: true, + noStandOff: true + }); var isOpen = isOpenSymbol(traceOut.marker.symbol); var isBubble = subTypes.isBubble(traceOut); From 5fbbac5fd1ff889560b4ebc878e8afd4126e379d Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 30 Jan 2026 12:32:17 -0700 Subject: [PATCH 21/26] Enable dash styles for open markers --- src/components/drawing/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 1eda8c31e7b..861df3131a5 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -965,6 +965,8 @@ drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { } } + const lineDash = d.mld || (markerLine || {}).dash; + if (lineDash) drawing.dashLine(sel, lineDash, lineWidth); if (d.om) { // open markers can't have zero linewidth, default to 1px, // and use fill color as stroke color @@ -975,9 +977,6 @@ 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; From 05e851f16a118e70e7fd1c0939688b9495c3b17e Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 30 Jan 2026 12:38:34 -0700 Subject: [PATCH 22/26] Update mock styles --- test/image/mocks/zz-scatter_marker_line_dash.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/image/mocks/zz-scatter_marker_line_dash.json b/test/image/mocks/zz-scatter_marker_line_dash.json index e5b7c56f356..44ffeef3f01 100644 --- a/test/image/mocks/zz-scatter_marker_line_dash.json +++ b/test/image/mocks/zz-scatter_marker_line_dash.json @@ -7,7 +7,7 @@ "y": [1, 1, 1, 1, 1, 1], "marker": { "size": 30, - "color": "white", + "color": "rgba(255,0,0,0.2)", "line": { "color": "black", "width": 3, @@ -40,7 +40,7 @@ "marker": { "size": 30, "symbol": "square", - "color": "white", + "color": "rgba(255,0,0,0.2)", "line": { "color": "blue", "width": 2, @@ -57,8 +57,8 @@ "marker": { "size": 30, "symbol": "circle-open", + "color": "green", "line": { - "color": "green", "width": 2, "dash": "dash" } From 869367eefb6a2c546de55366fe9a2bc0e9ddfadb Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 30 Jan 2026 12:39:28 -0700 Subject: [PATCH 23/26] Fix failing test --- .../tests/scatter_marker_line_dash_test.js | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/test/jasmine/tests/scatter_marker_line_dash_test.js b/test/jasmine/tests/scatter_marker_line_dash_test.js index e9125c39c96..7e5e2976f58 100644 --- a/test/jasmine/tests/scatter_marker_line_dash_test.js +++ b/test/jasmine/tests/scatter_marker_line_dash_test.js @@ -63,23 +63,28 @@ describe('Test scatter marker line dash:', function() { }); 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' + 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); + }], + { showlegend: true } + ) + .then(function () { + var legendPoints = gd.querySelectorAll('.legendpoints path.scatterpts'); + 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) { From 9361e7e93be907e130fe73bd44abcd6d0c03a8b4 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 30 Jan 2026 13:02:54 -0700 Subject: [PATCH 24/26] Add basline image for mock --- .../baselines/zz-scatter_marker_line_dash.png | Bin 0 -> 33882 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/image/baselines/zz-scatter_marker_line_dash.png diff --git a/test/image/baselines/zz-scatter_marker_line_dash.png b/test/image/baselines/zz-scatter_marker_line_dash.png new file mode 100644 index 0000000000000000000000000000000000000000..e00765926e1c220261ac0b143154471dbfb0d029 GIT binary patch literal 33882 zcmeFZXIN9s+CQ2ELJJ+~9R(EvD1smzlp-KfL+GJ5ktV%Gs)`DVG!dkTbO=ZlkN_f0 zq!(#|g7jXb^PfC>Kl|P1dY|*{d^*?v3m1?zv)0U-x#zxrC69Hq)u<@gC?F6B)ot}# zdJqT<4S_)8$Vk9{JSDi2ArKzO?ORF)eikcfQ7~bu&TAlO;FseA9*F|J#4x+z%0oReCp)eFatX%rPn$hBI6o zBQCEdUKL4-VQvBbE`THIGe>XAxCJ$@9<~!Nl>Up~oh2tB~PEDW7ZmH)Rm8MrX z4X8Q~7o`llO1e&5d>h1fS9UYvm7t-Xpkz#U>KHOb?ca@2u6%bLE@Y`J@Nf1amF4su0H?#_`^?ZF|mHz?o$3^Ia)v zw8}NrEc9g8xmT{0EXD92SIj-YXo?loU+P|gA*bNmJZk_!3Q8=Hc z{(?{Gx`dr~cgi(2fjh6yEsWm%-o4QrT>-Z6&5wbIz3!&hAFNv`{O1yk=tIsY%esAB z6z~T_+t&J7CV%%eM>_K7DRfiVwUt?aLG(udDSdp3KOdHnjK*mjy(#Jn!-4K}`Bu3RaH z+QZ!R0F99Sndq7|dbHG+XY3cxj((iIO31wTy}a{T-i*!aSY>CoROo5Y!FG>ALLuo| zB^aKNBuD3rCVS%fH0Qy*%!#-@`L+v>c1A2i$wSVzx=-0AE%s*b*?;~HU5-P3N zdoJ~p>|vx;_iG8yAHjb*RG7^l^iJC7MIV*keJkJp!Qrm|?vPQANu{&@)};4{x#zbV z^X;(;r+G+v3%p5cEF@{CHiF*GJ&ZJ}1qjJX^ zErxR3p~q$YUb{;}dL?Ez?95ksGs8K2My;0CA+!r5SGcaGk#MRizv0F|>&+ zj{6%eG@2jmy9XtDQODFVQ($Y@f_3dVsH@hIlVnkl(dJ^jHE{Pu+6!(BujrcF@2@Fa zd*g1=-kaHHoe4q-)G^GWC|9*qc5%my46;LO*(T2z4`Rugt+}otKi7$xGPcrvIaJsj zh5J-P;GbmwY2{nv3gA&?{X-7sm}_^3P3OKlyrqh@bbR_LvZ|dXFW;=Tob4`)k#uIa zA1}VG_-eH;aD4p(w>yP5VtB$e3}=wl;E-VD-Q6|D4JdyrT&L>%|@}`*BRztXRb7Q-^LwkHhmqd0=@s?PU>~?X~nux z47?>tI)mhXE5Bx9dxlatvD@*!bu>R29%eM%n|U(_RczuM_T_FxPaid8XtnxB$wXo@ z<<%QnZ=`hXzb0qm$=tJ*GOSylfO%5r=$8C}G%PTn!?Rm=WZhXhIr?MdAr972JXk@N zcM7CWj<k>*I8y8M&yUc?evY*p z#d2dL&w9BXxvhh*T($`tC^Ll=fvMDNiJ}@J4w#8#)>!P%nX4Gkj*(Xb&sF z;{CUO-$tPlmuRRUAx9nOa1}>mhzpxLKdRl{hWUI>wOYwMgXHf&9A_rS!Jc_2JW4IF zXb3>tERc2?7wzPB&#URqr#QJ{P+M~s4esYox&a%ZsC5*aXR~PgWHc!f<5PB zWwTCOM=8e)yGpGDX(K=&;4!e}B-e*9G;hqbPOQ)bkwDimeUShg z4x(B^f>f#Ty`ZItqs~~Rk=yB2!ZI3Hr}v6B9(zg{FFzJS1&U*!OF9ZgR5-q++camS zE3hJ3_YwxY^)&t=#W5xyhG`ljUMBtd>Lk%c3 zfi`TQyc0XWyGId{d|SjsKTQh#GnL@LX73^DZoEh= z#3Z1m_1@uaun!BaemL!TZITIxQ`Wv)_3@P1!cS?sfaucsgVo&jy;dTmoW)jnj^km= z5QU47BH|a?RC;&!DwkE{>nm2u=>oxri@6k+HvH>{uD8^8!z8%Ed+@Hp@;o+`S*8DX>m<|XJ-uscLXU+4CDpv#= z!d(BNrTf<%93NgTy5knnF!{wdacw^t3ezm`{qERMsK_G*B5`~<9QTw2-AfWL_|u8@ zE=eKL8Z$~^^;1h@*;sfZuLV`2U`-P3294$;?GFw;)RvO{{6unAl`WMOaKy0WXO>pY zDlA;n#Aj@)(E?D(F&O*|1=n#2I+Z^WpphX4CT+WH5!)z%BcQv8^M#o*t!NkOi>*o(Lq{sM+YTnM^sL*Hf_@%X$62MLnOcgjCE zAUy3l&TLby;vg5{ezbz#Jcw!20e>(c>_eu|w7?qh(7LbicA}%K4y( zu3;!Mgc@D5bAhsYK0i18H7U-zlGHUa3L;&zm%&hkmagH4&6SyAdD1YV6vUuN`?2{MiIGK-VZ?Q8qziO`HZ}?$ zTH|Xa7w|j9fttee44h1cD=6EzKye}7u%oN0^-M_FP{<88WgS_wkg+hhu36~#J&;!Yq=m>M^UuS=X5hFnjxkh(Or7ZV{~$zv&%4CG#G9DUMuBg+gL;gv4}hs zdQ_AQx(=a$Jk~Cgk8Y(LzJNEM6$@E6uZ0Y!%LN+fh);Mt^4cm9d`gVel4ietul-kf zL=0FGX1W##R0uXlk}WcZ);%f``VonxN8QvKm3}W|Li3&hbFg}m0pE|}`F!d$l4^N! zzLW@YzDQhH?~V%76oowJZE%nn zoHW>goJa?05`@qt=m73B@AJDCpPC{K@Uhf;J?phfL7eI4=<<|2MQKp7Xv|Mto^`#6 zm&8C!88H%U0*D%-nSy3P#Rg6otiTEm;epa#apFZG+Ottxs8!N*hm2 zjQzziPJW75X=@Zj5}c>?<$6|OPmVb}tdcP^B}Bx$q7$~sx2Bl}l)j2!BiGb;^-~HgolAv-u;Qd;0k1%E}>Po*4c=>b=zQ0vV7^6xz7z{-pHW zTG%+#Fb6wcncLEY3eFy%1&1UH`Iq%@xU zv zu;`Fj(dO9th`t`p<0C|mqrLk`UQw2(vxVvjGeTBBAfe?z7Qi z#8pb>OC?k>$P#i%g+p>CG^b_a4=P0%XIKQy&@&)DWx|Fg)pjUbj; zkGz|RpLdY0380f8;ug$|0vIiwQ9dXGl>ki3I+-|b`nBQ;#S-+m!m&>|fwbNcuPm>~ z{8NKAIs)&`iRW7OD#9tV&vdq!O8qQ@_>DG06r*M-(8<#{F}nCc{v~1MfD)vBF=Cm>c_&2gQ8$bu`)pc%(6uQUI{M4MnFap=e&uNu4O3M+-s|A) z*|C~Za8WR>S41`zt0C*qPO8(k+rU%_QiTOFop1?iQDt|Bi%Sc9A!_ z;9#285EAG0^YO)klbJ0|tTI;4Y*Y(i0USD5Y-``Y>vz)2(<6sVp7k_#xv=PRLbXxr z>+kj#VxS57N^CA;VH_<5)%?we|0>HJn+Wj0|1IbL1}Xk8&hh^j0O){)$foT%{_)%bvWgw39z!V%6ne>ug^ybpR+)N4|iT9 z3Y&H==DuA!FZtnr0JlhUkbB0`^%fe58do|etN<+G`HkN!PCq_1h&%S;)o$Bo{s%&% z$Of<=cf%)}HfqMu$hVOQ=1zvYvb>`88({_o1|4cn|9x#LxHg7seWdj5o8Z0g<@Vid z-$4m8;n^k3REc}^7$)Pp_6pz}w^PLIw4Piojf4g6t-L5UttMbE!WtjI75k^{plp7^ zDD?WAjRioND6oVswZq_r#vkmp#KTWwUkZJPgZ( zK)ciwf)D1BJVAmHWgc-?{@~qHmaEDFTFHv|txQW=V=}Z--a;ugEKwmXtX&09p{z2j z1n<`;Oh-sZXXnfHPe@2ILcwtuiJjv~x6*|rW@4znlcTABvNG{wSSh}&7-T>$E8;yDkg&vX zzbF-QbX3?4+l!bRj~vjiu--U~5UMEds?8U&(=>(9Plp{}zZ#hS8Z0u_AFFg}Q-MbP zdiq^I8;nfJ_n+D)7~xh{?kTCOhL8)1s6AujFE*(ZGf4fOujmOD!HtDGcu(3M+$Ct2 z&XwpDVkGG%Wehz|IjqTK=%crbUGY{bZf1MBDcq@hONeEgpRg{%XaBy867JQm{qOcd z7to-D-NqZLW~=sp^7nGmsV)(hTD;H!kfPtB#(Xy|Ld{Vn=Ip3i)9U+Tw(Ybxhf>lg zE~pVY{Ps^BYQEB>Em3+$Vb`UBn*Y|{;5weCaet!TxLoyVTxMw_*Xhwy)@ue&hcgr1 zoyn%@*pUjS`@i%GQN_vCkG4Hiue%n6Xbpg>TNIRr<&Xm|4FfSiH6sfL{37$y(Hdv6o?Z3OP34ruq~-KJ!PjAocEgLrR7QzroI z>2_b1$}bDa6jlNs*N_@`v2$N}WxxxP zZOMw{1f^7AA}gy{Q!XPsv53TVJZdr$B>UAA0nu~!wtshfwopl`|N8QbDp0iSdui=& zwu3*7XRFJ8S4?p9&eAzY*ucrh&MB01lf{~d=BqjTGtxtzbLQU7gyK}h%7I+VSr@GAo05t(h zL;@0ob_u8cTO#!v%^MC2M*woX`TkC-K%MV8YNU3#r0x|z)tdAItQwQ8`fPBw-l^jy zzaai>z40s&^q;pS^@&PEd16HWt{i{k@m7p8^B6Vu^Lmf`Q4tc2Q>^8587V0?i*={_ zwACr3m#s?Gl+RxyU)5Z*|1)NYzuN6bNvE(yJzC?{C0-CZ60UudI&UBRNd`x2djWIf zftc_=zHBuPkBEB{HM;p#R$nOd(Is!(;1_3S)tA%d3{OEwEXeNCqA#+L8a)2O?q+ZW z{^*iv)x8>dj#v^%3v?Sx3t>S?SGkU(I8&n{7~~-+^*@uoZ_R>thdfUY<_l%9rP;Ca zCQ7s=SAAB{1OU3`F#(C31t4nDr{E(&cYgr%oU4HB$Kj}22^eUOMnb2TDfTj?x8cuJ z;j|0DfILB$g?muk-r8g#W&r>~@l@>B)uX97ZFd&>mbm1PH*ONJFoWR6lf6{w6ymqP zUSshDcpFt}QOvUhaIM4^8U?dOy^jx^=1PbwCPU9ooKq=le6xof`bnb$EMSQ@X&C-_ zg?gwQ+4o%6qY*-+dM{>m+S=NxYimE(J&;+i*EjwoXjD=JA-*zL@9$+uTBLto?fI); z&tU|gyf-R&PW}`yuzt`g^exNi6V=W_L zU9=@yHL~Dc?UQlz6OM~X&Ho*#nSAgXhLlW5t&4Bm!_0iX+s$whx#J5A0C8Z~6n3PY z>uAbKINw??Hc1mF@gg=Fk;_S$)>ExWj|pwj*|Ba;<5sQ z2_@g0lgCgJjut}WAPVk=77zMVmU?i5-s@B5&_r#lvKco@vWED4wNo$Fupz*Ei2Cyr z|H;%BEfc11s~)h*Q$*YPfOk;+{}TTS$xQ9+JNF+F1z&z-7(wM$uZt=&&fZF0iHu|s zsQHO`(SOw_kBIhRGV-&{0ThYOqh$#>*&Pvzp*3@Jzv_$+<6i>ctI_@I$HHQOVo`$$ zu_AS`%!=G$Jyt`VG`Kj@x>1D^y`$td(wr zIOz=ul6c)_*W&E-WQd~4k$ePZ+ti9~3<&MF!r(07-fJM9puF+s`vAx7EVt{* z8lKOkq0r<4FXZk&Zwj8IktdOc5u+Uqrm5F;6C&mW183D)#qOK!uL}$dwXAt`QWr8I zH37zLGUjT%^uPwWVhWCJw|wev>i>IM{>mPj!%tEkG>4!^`x-*hq?eG*DN-@+i+bKn znLY?UH|e)3WFup`YMeP{K3b84M^gOy-TwwBp;^c`sZ z-Dw`JOK*N=Fy!&Kbq}N+!EnNF61q{K``-bLxTrqNe=@S{-7Q*)y?e@LbPSB@4xnbF zZn*hRPfyK6IgEr1PStwZ^@}!M8sWW8AIVNfD$~-Ra33Yqa^GQ38NiNRnc4r7!u_2^ z=)zDDnC0Hr8@Htq=BpEFlR6>xxiE4=ka+Nj{woI{M&4}VDF1uFTVy1-rKuA< z>8OuYE5!@f@mua%h;Oswp(GiZU&$M9`Tea0_^J)pBivaD?5+QuCstbGs5x)RI?P1c zeYP+%Ea1?oyhZ+EtnwzV^%lI}j4(N*|1>$sO1eaKpS`9|Xb}k&{U*mI{WpV3XNwK7 z!8DK~iUI27MxI$OwD4$f?PcA0Ba^fV4KZh1_12fCKFU6EUq9xN6q&dB++LP6)wJ){ za`M;HbaKD9!}+pU^i@V!_Izw&ijYHr&&}o6j4lET8f!xV(88IGDCeLBpSqFseKvvP zD@tFGEDT)d7mF&HaT~JM7u}1!r4?cy?(&&oE1l zz9DtK_xA!T$5HtDSJV5ot-t$`3~`_iAmtrk#a%ybxGHkR?)f)^_)pr{gelgBp@`S_ zZxanmhK8K){vwW$q=}~$c-NFfrS0PAq>$P0dZ)pp+O5~(V;ZNlFY^24#?2Xc5#M)} zEpaLvBAW|_m}LhuhdJBZdtZr|=pc#reLpJ2Fyo&4Es-^yEJk=x6>qNdroNWwVsB4> zL04{aaA$YPxE3MlHieYB;=eXgTkgAVkq_!L=`I?F6{v38+Q-+Mz3eT?4NK(<)qM-Hrr*7N)4l2(NQ)(_e2$P}z)T>s>kzjy%IF#8|!6jDO zRiB4hsKhK@|Mdgj80{1K<@4jCI|4XGmkQSS*#%Vg_E13U7sjx_LD%RxTJj87aXdM4 z!50nnm~@Phdrwpw%ymlXt~8J77#ZS1Y3I)w}A1CBs5rwaB?B ze%=oo7C5mR5XJIWuzze4els9H7~<9`N`f%4xkvP+4~4gqHc4`=(6z(CiI8aN6q8b& z`)g(|BwUoeskokA^9_<99WEf$%{S)Xc0@}HIO`htc94|=@;8zTt7*pKCJ zW_o`+4ZQC_h}LJAMbr%AtXeKeN*X=nrH+&b$&q0iAqknzaOU{@bUCZroi3|EDh`|o zLx1I4pges?JyEK4Tb~ebyrrQ`0&+pD=V!WtnyDCy1ZcHLlJ~9po=8At)Dv?rmB4+u zdEl3s%x2s+!zLQ=FoYW4q##6Bk#~$nlefs|x?=R!!Zk&jr6%GxdAGSoU_mbG4^`~i z=Xmp;X@M4fmMrHXOj?J1$ZJZ^UbH8XIJ%Bkv!OFKkH_aPYPn3v;3<9)llRb7JRhzG zmqU|!*_euNDa0!6rmP3>g^$a4(=Yb7`g$x*+#p6vcPI0{T~h zHu&QD)NJJWi5o| z-11!-y!}mj840~Lb14nzLBx6_LIFEHlt?IXYA^R)$B&1e9a+NBQc{P6Duj@|emXsL zVZCY{@uWwYOOf5Dk`x2QKbJprgroC_?)MfL77C(|7P4b)@mT2TuATN@2MGOp1ZF0N z!*W60?^^6U+m2UP@+gGr@#~E~5@H>j@;#!?B8vM8-4tU5k<;~0TxuuuJc^VRhy^@uO2zQhJao7U`SD=%ZNrSm)xKO(48cO zDWKbvU*;0dr{xC_S7&Z+XDD9p zb5%WI!-GGT{R)?kVW_O}5@s$+Ts8xhPRujGRd)qQ-b-gtIi_MZsE-GW-V(v*2f)C(HAAH(;%{v6Yeca*xl2k^=PjEC?YPrB_HICc~JZ-q_G$c<^%u^Vc3A0 z(=AP$0k1v9+N}#UOi*8fcH*3{iNQnqA_pMbi3ifYF2IR0lOWC4Nx6e~;xFDsm6^O4 z7pKJ)MMvo?fm9+Wp9ERscxCaZ5_V(4IYWiLeo(P>JsJH_j|x2|HZwwTE)PSUP#KV& z;2~>X07d9Lx*9fpUjfBK>JJkx$m}J?lcSI7Ac;kCfyaN^HfH$Ft8ul{{NY;KNWoNw z71Ai~fNj@F&7`4I1Mo{XDVeYoiTNWIfp;pIdn13$*qhaQ<~%72OWg(?qeL{&ZDeS9QsuA@ zAip8jmcV2#ChyZF(f2UUxTlg|c4ljg2Q>p(^8RLgJ4@-UM^wzb);Y&tM04118A@V5 z7wI!AomTjuE}^2wo6+qBhNmBi?<>`iHst%R_def{XFyF65W`MA;t`Gs40BbKEPz0&Q!lcIHX1&yENcYdP6Ns$AL9T?wj6lZJf2L79qglCu#cjDGv{T2 zbp+SA68+^p5$1+?qaq_e#{VN?ns~%Agd8P=F0JqA4Pg8XQM$*7B`YKYpky&>p9+)) z#&}vt&nwDijqy?Nv-x>zxy*YR zRq20eBGi46*F0b`$9Y>%Krn+b156oO5yN*pBZXy9yL?U+-wz$gAEj<)!d>}3!e*k` z%I)fh3GbQG4!+KL#kHK4Cl3YIt9%`;$qUKwWu0<+l4_jn3Df-;)- zl<|FXZ(Lw&vyE@WEWY|9o5U?#cX&_Qc-nX~MIJVifO=;&Nj#cXNeIjTFmVsszZZDs1w{s9{ghts>{95)4}}T@{7xEy z<0!I*hd-|Vh}M9hMvH^nXJT zj+?p;N?-fu2ju(zeE780eh{l(iU{D}WxBFR^uSfSrc}13{N7mY?U3cu)Ul3wo_BXc z4sMAAo`^Z=6+hN;lab56^W_~)hOll{#HP0>r*HmO6A8`tl_>&u3MC~(-adil53>D< z+2?Esec5sH=QuByGWD7kiJ7?K$LrK-u}NQT2Y$Ej&5$*vTvKtU%#@!~4UMXw=~0VK zeDgA?>nnd-fnfbXynjzU6XO+txiYE=zoCm9;_Z>$eriI&PKBc#MYGUD3V+O>%+73c zX_I51^f+^b^l2RZ4Hq|B>l>mOFLneCf{0pZEwSsO{!Y3=tZu z48#R3VR5|{R39KMwgPyh&qUS3gypFQZGkg!q| zZeGzUt4-Bow1?O-4tJ0L8m~J$yLU5N{8xslq~1 zr0sH{u*+mR#qdVA4>a}?Z0#Ydm zpD;Qfg5<1u_Snn6dM?Tr^LA3=cQ&ZgoK=2IcadoR@%oB79R(c3rbIs3gioX~XNw&} z4G_VUT#0#M?4^DO9@R0?G|6bc`ri~j6@&}#xBQBS?xuJO zn7ujA#D*@lgKwm0Rb!>^zuQowQWw`i50m`^CMA|~E^ir^TEg1n(_7<@Y zzL(W$fFaNl56lH6*<3+wHdfd6h0WHhYwE-PJu}yu_vz<0>K32KgF7ha+LkU1Fln%- z)d#z{P?f`xuH@xquNQsl6l=O`R9Wx+f_9r1*;motN1zW|o4z>P7wR{6-hd=J>vzBA zQ{%K7l&!y{0JfCibIQ#qsk8N$nj*S7*)2+2g|v1!^B!Y|G`k1pX~u!_7QwrQmF^CE zE8mwMv2(_#f#FK5Qh>weOQ z)vR>QT2pQ1-k&mKqcB&5ON=$=P+_3yP{I8XyBHGDh`OB=%gJyUqbV5(9aNE{y%OJ3 zvk!-c@5)zNyX%$MzB#T-i0Osn+^a0try7QqY&l*(ZApy<8dUEMulk5=Pu$`T0OC1C zUA{IF^|D>Rm-o}paqABRnjLCt@J92FBX7S8e@pqA|bLesg;HtwYER7p*M ziqDR@lqP^uNXL+6K|(8SL50Y{@SB!dNA9eS8v)fat2`}R^z~S71q;q5 z6cEc+I&)&Z0n67($a)-sl)7@zVZTliaEaRP?h?;en<_opcb-X3+Ah5&h*v&5Au z`evV-`;Zj=Rds_kg8_A8;s_|LFO65Z#`CE>$qUbO6%G-w;Y-^B=Wf9H z1Z(qCi(E zt@S|=tNuVO{j&W{kem@9dL+28t z?Q^FtLj?(DK-Wv*jN7HjfM1CS0BRQEV*nNJe+xH6WRNTr=;b__0O^~5)R3lV!-Y1k zz@h?fEc}x6;X<}y13|s)84jb??E&?ZLEum5Fo4Awk(|C09-SJJ)}P4?smax#(5ir) z1zwi3yexj)=1gCegNu%f*P8QMJ^a8(}T5Ktxtt^3=o^z zyEbF6Sh}%CgJb%a|Uj?VRzNV`H1(!c!QgdPRO zkKgI4&o@zFLEtd8mn}clPXNY(LI8IsHVTFgCr~jR-@big##sceO3dxuCN4n;2u!KRjV;E5*K!)3XS|eCOzbhnB5bA?zWNvy5YVygri7LT`G7s zlG$Pdj-EnbO!*Y&-rbHLY|CR9mc%pn*R6jN0=9?1{oZ9bf~kF>FWYWox@pOxGLemA zz>`)0rwb4;j{zvIB8MbUlkuIX*-6#9Z+|ru8C~+s#GuX&Dt}|u3-+)(W1OW}eqf)6 z9k1~@7=pU;>1s(hw)c! z4GB@bSo3X~jVNNyp(#w04&wt>;s!Oho{Nwy2!|2lVmIlJ^EV z8!%rOttp^OKKiT{Op~rjDUJ{{r=2aumSF@U0{iiq#%h) zh%Up37gy1QI`+xc0MP%CYCj>LC=3jW|LR63G6Vd|cS?~BF z3{73FgLwil2S%1A4z}0ez;;K#-#c277Ck_#kU{oA*i0D8(N2>V{6q!_7# z0{@me*j-A#`EP6+A%m$UsjVyG+gS^bgm@BXo=0;ihD8jy;p*{rQ3-wo7Y_WsOidfa z?vdBw?ucbVB}F2qTbK;({VD>EF-Q7{#}b7?Nr`m2P=R|TK6w$AK#HAZ#z9=MJa6I= zEX4KD{$BYSzQ+BZh#?;{M2RdF`q4a_g!8M9TvhKO8BNck_pcC-w|q1zvsSSif^q!( zpnho(q{Jc>tWBFzLQG6Dj^z8FQRiypci<1W{AXW8-BoY;2ILS6jbX%S_qL|U1@CH% zujl8QX*a1txAwW49D`(^^5lO}ZQA9;B%&blViKT1I z4fBgiE~`wf!MuCON&2xzIcGzihWeU=f0%w0eyjg{f_aMRspJW33a?*xG+FMsBEM1h zgE3gCO31>zm#v56%XL)9?c2Hl%*>eFmle7uWFY}RT`$4f)jh%C5e|Mt=vp$DL=t4n z&#(9ML2alfbxuef8%Y#>NT4PD;{u0`H6j zO%>Bw4<)oS23M_)m_C{{lApZ(Yv=xZw!+ElY8>C4lRG^@m`v^r)NZXLN9XI9M*ut| zPzj1`;NXnERHPu?)C>b$lH&gDYK(P#hYkEqm3qb`4u{!lqJcf`TYG?5YxAMT-NN(D z46!w4H#qu!bSkd?x#3KOK#Y+Wd)E!zak)1SS!P2jOE(>67gq*6m6HAaka@_|!x14Y zl2NHz=beh^?7Z#p5LNQ(jgyS+?}=W4*Q{rw98*I;*}Hup>)>Pj-6-pw?XKK}W47-6 zb!Hp|89IOQ3P3{E;BXwr{j07|#7Knv&6`+?W}VDOZE&ypYinQb`ssFr2oR$i)+dq1 za@Bc1K9D3nJMy<5Xy})zy95>TZ%H)TB1~tlPG9*y5iNks>NHGdIF!3~ZRgjY?RK#! zbNq?{j}<{1^p5|3Zvk?28T_VWOK)jZ0i`q&U+4Z=#~Cn5w?Xnh$8+PKn50f_98Rh& zl{S0$_R@J>0C1{@~~a7;hzHu6B~@qv?G$?%_^ z+V0Zm=?WQ^)UmdP%!7pcxnGlq2oRejE)5kHl5?qj;h?xY2|>toSOP)D(xtZ`igf^s z&5-dUb)Xxy`V0@EAs9}(bERRZtqK7Q|6LctFMAeFAP4FPCc}+`aFBZHWxzE_zI}C> zsPS-SW12P`puz!f87aIB=)O>xKp^~zF4hK4PnlDl{3qCi`t%Eb_aw4{jT~Te{|S9w zuVIfI0?|>J08w5{_v5?k5PH1XCKht+=MOL;;fJst5XL?|mb`kULmn zZVJNPd!)3C&q^F%pDnojrlH=DU7*LA1*(1emCol5I|YR$FWK+cC=ERhaHql|#va#p zAkg8YG$_nKjwbW>Pr-H4p927_wZ!uqm{d9e)}FZpFw^XbfaT&XU;xq#C!rdY<6RGf zBL6vS0%%s{$NMk*x0N~`w-{bZk)GY^2z{Tl$DFR`3;EDgaJ01XxEi^yZZhlAqk5EGDh8Ly?P*o zo&`1wsum5v(HO|-=V~XUTyy9oxTFO9XImmdIxpw81G6Dx4Mdq5j7@fghy^MRP6WCp zbPd4E-T^CFf2pMcoQ&WBgRz(qiAX5Yncc(E;io_jXBaX_1R*No07>x@8yk*bSsi*0 z5^1d`=PqZB!ACy=b=kS188)DuqDOCyPozHrS;%+>X$Hx+P}(bL<;TA}G%sn(OxYlT2o zcw@bO``iRn3OfBF*lypk5C{m_hxU?q|0$Z?2S?0|$ESjPIOXsQmy%l5I|VPuF=9X< z1!o}OLgk+ph_BSmzSMjNR=o38C=l2TzTNmL4(JJJD^LzyA|jzk7Vh!c+j1V}?VK#EMu$N<4iEBoqrpd+5+CDVj?|xu9GSnTkrk&7Vtm^AKLl zMQZB3Ki{X`*TY6^)mTzF0p()}M0`K6Vh0qOm!;}~t}S7l5eFZQKtWWnJ#c0-T4vmE ziFqNOdJ)ibdO9SCEXk&TNO@NIAisT^SI~26B(+3eXt#j??t&d_j1M;i=}4_JL=KD` zrnZ)>@?(NP`KMZWVh74^V`IMW-!r3j)`Y{(&hsC7k=7dMay-21byiz{&7MPwzNKfL zJ7}ZYEX4nIVSw4#v-7gAB@jb#FFdBzV{e5He0<;Xw-#Wn-`=P=SL?fgk(usmlcN>l zr1`anq^F6eCpUkMRdBuS)*Qb#78d#JX0)07=bE&X>}x|xKdy~+>yW-Yx)UOdPHvB; zywNLC^B&U;+kqqlqHAG;A28NM*afgijpAUmz2w7uS{sj)wgj+!;H{TVc&HPI$8w&g zxk%#Wt`}-t`HD~oY$IHgKJn6LaVYiDaB<;ut0_%q6)-x~E>0KRYPdwSOf4V1tb4JB z5_*lT7r1Y-ww8bog3sF=9SzZWvFSu8{tsn%C=+=W;G@kBXz`7oF`|M=mcZU&mW9*i zZE7X$#@yZ#sM&;QK_t`M@2v8@`8Op~YxJVmN3rx&Zc8454ms*vAKm(W9`ik~s!>Y= zx_nlpv47=PQ((~Y;?q%t2bltn}IlPh@O8wG>Y9+Qb z`Iy}axzYQPvi^Spe$7ihn(up{1y2iYPrCUIF#FCDJbM*lcPgWX^$N+8d`<74Yl(c! zj+uUTQLpEL_VfCqwYtt{Q60<-H$KgK2LKCTf;ahnF)Xq5V?Of-0eyj)aF z*CA}D7C>>v=;8yAkw~cK{GTaOvoOFheO?k8D0_VTub^<7AJg$R^vUs7%+2NQ7?Q2X z(@9^x&@2e#O2|^6um&#&q0Om@(|F;e51NTR1~?jE&w%GRp9MMiY`NKvB8h*Da^zA-G+bynczTK)ts1MnCB}e5f}G`m zUO4XdTWo@CF**8E#q-45bq88S=uuLHyTn=b!7y|yfV&7}G6gJxHR~9tlH!e*7FHy_ z)4o4~bHlzt5o0P*9jt{^AZ2i1#8Sslg^h#3qGuk}h~UiuyZdU6%$=Pr89{wpnU zU3SAI(vgc{_)VFKNSr5B1NhbcJo6K)pMc?M-PfOO_n(lDu$y_+kW9ot*zo$ms7zrW zON?9zk8D)(Rs1FSBPiY>yAREToD_v<7A4BgL@*3?ksnfK#2hs@@NIB!Q1-Ssl&!VA zE4@b|B@C4NtD^CO_ZK87}KK zug^6fJb05ZVs2;l*cDpOpYE4s=)_!Xjzkxfp-s#`)qB+G7f4L-=cYSP*q?Lv=lbiD zM~lGEQ0SAZ$y;f)ph-2jDY?pCk)$iE*6L~!{g{&&y2hwspfg-}i{rQmioiDAg5LvD zsPB2I_;S5&yM6$Q-(Jg-fOcsC2qDFy&yy$-atAA7d}t>kt2bjbcndf zk79Fu6Wp{_|I*vWTnd~)Jj?7_-TWJ(Qr6jY*<#k&HPhvr0HOKQ9Ux{sXZ6SH3#}Lh|kjmdR(ExH{23&`9&Wq8L0&B7A~q5rI;MQ}h3&e!Gn?r;WwVX;vr-1efq> zr4JJ$ElsX5FkaI7>q%t{v2*m>GwyYnS!OB=$jo($nkT*02H#j~pY$3lqku0<(E{}2 zp-K!3DN7>p`O~WEGU`Rt2FLuMf+c?c^_j=_T0;c(KSe!K1GEqaE#x6CKR-WSDms79 zBFc5IEC%@;f>(Mq+D0-j%zv#f%r5i#pQbHgHY2*jL=C$K{#>;*i7CTh@4zWu5}Y1W z&G)OMc<2+04FHla1}<8=QNp()oY1)dLw_^zV$59sCRkKR7L54()PScIjD%7e5_t6) zz=iF5(u=9*v=UEZ8OX##&6N1X10-84dR11t+-rPu58OP+ zOFX>x1dS~LqVK~Ze*C@A)5|JGrPR3QeM7m;t<0-EjSunCqCf)7XPjCl4rw(Y)$w{hOPQ@BTK7 z(_AaKc<1#>!s39h$&i8Vcd^T1a`lu955Cj@HC2t(Z90cy-<7zm7-n|MksL)HlTr&| z6^vn(Iw8-eXKIszZ_1j8OPUcoi~vejkd`onrUmRWpvzhb$){aYbLPwBM`IOEHaanZ z!}7t@@gXPFNovw1FV)=4=PWXnsE$B=vBw!&uk@J6ShTJv;_GWegZBz&Y2vQQE(W9( zq_#77-PDR|3%SA%WDebe0!ij5v)AJ72{h5c4qi^nVn>}xNx_varU|}7Y|(XN5GJ-d zX4fjIm@7FZNtbw!uBakkc5N@fK4`ee#$1+;!5QT1F*%o!FTls@SGG6&JLXWrQs}&K-iS_Gvz-EbgGzfL@O^)KL@)yFE{}8p4|j%V ziCL{)F<%7S1(ZU>$nc9##>H74MHc;!*3w27xP`KS(dNlD5der?#GwHvpi!GEXpom- zT_RF54I`-?m(`!>m`rje{Lp%6OL5C(ZI(;j5=SrSe(5iZ&YcpzivMgHu7vo1gCK;uX0DLD|Mv=$Xm3k*WF zd2Qvu0eR~$Qwx#|ZU2)F_jQL4!@4y064+X@xVrSfmlEMAd??>h;0Db%kjg*O7K4Hi zJ~z)c)qad1_-uGQlqfy_3^6`*>}L7T=I;#yXd^Kau}O#=gy8V6Sas0<8kmi(^ayqS zUmt@Z-a3ytczHQa>!ITvJjFXvG&!TxtH0*Kz5o(aNG2@(4D7_1FNP7Ha`afu)73aC zEA6_NE*q7+`L5!QqLtyOwMTQp0EKWK-v-LJ+5BQ+{d4AH1-^Cp+G{gthBz>D*&f1csVV~Vy zFy;7meXLSb#2?Nm|UY=wZ<@{&c}2Ti2z%1`fsgtvLOD+y~n3vM%W>loyhf*pEO(B z?H@U25jU~0_pH2^Hku#)*#^u(rwTJ;{9OfMrq?^aW@C#kmOpi^lOWCrQjH=zjeYf~ z=X|Nv_;2*V5b9&?VBSfOH86 z(kR^>62s6#Nr!||!bmqF5`vTh5(5s6bVw+TFmwyj2-1k2cl`hUdtaP=an9X|&DC14 z7VBN>?dN&EpXYGgfMX(6D|eAOS2fPeo&vP{!@q4rY7ArF%5SH}gLlLl6R$8l(MH}}jm}8GK!#q^fk9iVS|(%CO9E8m(;J@$rSn8Z zbWcS>TGf&m{d?6iowEs0Gxay_=(88pGR|&Rh|a2lNmdK)gdgn>A)#s&w_b@t@6K?* z?nP~3Ro{N_tsG*y%DyJ@^#K{&nqm0O>r?+v4IK?S268?d9JGblQO#gtj^gJhN7Q=1 z?^_>ar5BIgnP#hc*3`E*pWoEFdOg{rHKqd6IwiHM?Kt#rQ0%iA6Z)AYA` znUo(qiC^H+`u$ENV&S(za-P5pgZ6goGv%J7qy-)bNsZ0~d#vDdG21uMeK`sA_5jhP zPrDma$RUSkibJfQoL9@pCix^9{$|yxz}Sigh^scrJ2mlUvU$o!b);34>=ui(LBJ#J zov8H4B5h?cw+9XLj@wHeEX)pXu?_Ru|Jp}OYw-EBWL(hUT!Y2+(@2xwA-&&-xYfSd zg@5ib`@<9xjkE}aV4sQyO;%X2w3{}=3N+Jy1-kXUctOK&LX^Te)9h}s!~0~9Pw1)p zPTPCz>ysbIYR$qv|3-I&7D8M;zdSR=xh{ClCFI{r*zSD1^{bXqLhoCGxJ^Abjc4@t z#}lO^Bw<{bYs<{g9#ljR&7RQ9|6q#>=8~dv4 z1+DB3A!%sSdP&nPsuJH{4+j{)e039iZXt4BAs15+GQb;DVjCJ3SP0 z1yng9Tzrc193Rj$Jkia+8`TOm1YL%4U<>AcjDi&pWe&TeUeFkNR5J3s(8F}7!y-Jm zwes!Oao^A~>oInUUoI&>x`P*=5tv2(D7@ZB%xc})Mc*nu?-`~xoFnLxLDh_+w;kyz9O4vBw!>jY?=H8UU?W{Ut}S%Ev{!Fm3f=zyKXf>uze;N zw~1HkWZ^6J9L7&(y@8jfQ!~x6PV!9D%WeUUS0XA>VZ{3ZeUrYyJ^s5OC)~eQCCHMU zL4g1S;DRAr%?wjoixjW+STUlB5;h^5aA~-cv|fE3ajF25soRFo$ISpiBJ0lHPy5;p zmOs}|5D_mke$3X~hnT>g;kg))SMVZeApKdPV{{Kze<$aOBK_q*RzRu{x;W*l*tA>N z46GGey1yLWP|&f-%duGAyr*;eII-;esYqk$^FIKVB z+^G=ICdcKR9zU%Ko9o8eupDA)N=!j-XX|4MdbP^L5*J;5%%e}Db&c><$boXQN`g`> zjklgl$U|IZ)kd7vb{dTyAbCjZXbg>Edj2EhRjJ`)4{wovB|XnVGlK1FHl7zpL?4l{ zH=cTkBh{zKzCbEoOx%F`vyj-n3;CmGZc2V4MrKccv7U;w9sK95 z6H*wRV;Pjj|H3J`dgyRo7Dqj!t+y!B$nCp@h||dV9^CC>B3r>J^P11=l%X z@R-nTP)L!vnd^&ihsTkUr$-w$*7V(Z-!K{fc(?WocDta;3i~30rbLu4;wBdcgi%nA z8DA^ZZ^4yuXeQ5I`8|NAGI6rrfW_2Up1%nn5m}@~_FWQULs6>9;*msYf(@)xug$GF zysEWvAmZ43AMhb}jc-^M9P{Y7S~6QK5%Z4P5L4vsDUz^(4&oX^ng3mou*{2@wTcof z6<9IxSMf2gu%LE3saVAla}{c9Us!vYJgk)2i_bhsx8h82Xn8z=S2UCu2}MzJK45W2 zK5@G&QwojM)nws!B!zLui_P{lD|-jBy^!Qif66CnQ4UL{)nTfTpQmmaPPF$CS4w5?B#37MVJTHSFbNf$QK_7biZGAw8%wyXVubvP`j);ICwEd<$KQW`| z67cDom6mAQEl3i3w=0oER0gfJq&D*-gSdR(J=lRwUx{W z1aV0kO;2_W?~uaWC|m4gP3cpv!J=9YV;R=(OZiWBk@fFcwgi%v&ON{e#kduHvzE0y!gD`f_oHYVdzxe z#PS8OnT-8KaWHm<3;X?mcii{_f~P3>ldFuv9kGVy%&I=AZsUBR^iRy%rC>whn!r#< zi|_>c?=fJT(r#%&cpT zueR<9#Yzo!!>n(oLOOF|l}6fL^K}|-Tuk@$IRp|U!-}T8Mqb@L1w5GivwSvacazh5 zva957Vid${L9vk65Y<@cY$L>Vl?O+IAZUCfGhA%iR5eGNa9hNtREw%dq^Hih3LmMK z0hkv&!@%)<0RH!iV{^c<^CqBLf(Sqn-J!(WlEgM#`c>Lt?&?BZ&k(VbhjuR6XRBn% zMrHA;bu@5got!!}QO%Q4*5uog@w0A5%wC7#F6i|i4*lwj#2`59u!ps=9mx@H=Cmt4 z&OkF|`snH2wYv`lwV()^^A&`P`xLybqs%%BuqX7eLO>A!s6*6P<1WncVHFrm#AExv zIo+o)!J>r@Z_+sNr*M6-k?+`VlZ>a$RkrqZ9%c1-l>YfEc4s++BoNwi(DVGstZArT z7)SV}p@c_Wo+Sex%N#L^)rtYdQqFZUs6jvrR+!A5n3?Wn5d-R_D1~zCT9Z~z8db6& z9Lo^?kf?JjQq0UWEI!J(AaJC6>k-j%4+1a2qgl|SIlX*>)U7Vu)3%&V9*_61nLsQH z>WH-rMMPu>3P_MKGR!na!~m>K&EGxP?K=cOf}zy}IR%^po7iG66|?*AGX|qd7et+;UExv`xzs;zO*Il$&^JWe z!P2onJ?>n%JFDn%sg_YtdEZ{ql7p!_hPvQtBxzsc34qPBLubQLzv9-Mu#t zNMWO#znGE6rn;oO4cCK{gjIr`DvI#xk?Td{w+BQBMyC@nnO@=}9R(!i8$)so4&m>{lVOm+mewG5`Oy07}-pkV*CCP3O;!b*x~~pLU{)BfWfR@Y8;sgyI=Ybm=bU+R>ceyu5qG-(Mf=Ve!1c zKvcRAYM^Yb(u7`vgQWAMoGe;J6=>ug1BHHc^1WqS92MiIsc3rnRq23l^WNOKF^l4G zYwVV*6Pzp*zCGU9Ze;&;-r*xlOAz}=Loe!eQ~k2s&+)WKn^j)-%<+~Sx~Yu{v9}QT zF0{d;U7-a4euN&|Y%EyVX0XzPqo;Bdn6kst@#QqhOezgU*ax;Uraho{>F**AJQ ztc9WU{VX*qJ7fs#ID*ieAEQr46mS~cg=o{TxIt5O|*8hS)SHYrD_6|2l=^96cCAU=iJ>Ip+Dj>#)f z@+nNoRa7x07uTY#y#Bd+@kbaD4Z%!L7)1K9&5EkRmApwsbAW2=X5gR5I@UZ!^xGharPwr$bJ|9fwjbjM;g+)p^vG=Pc^7uz-nF6huY}AW%ZVGIZXI3%J!ptNi zw7dS}riva6_vTR;_ObGpwNtH?LM&VM(cyPpPg&wXFZKk~b-t*F$zq5?7DB77Q6z8j zsYKwDqo$s8DQ1qgmjdlnMB>PBP%fxYR!M?<-s|N1NF@T%PD_)Kj^2?>9u*g{Icedwy;2e3^?57&~=NSgh^-V*Sg6 zCIStiMUp2ZcOoLr*c*!SL}#wG7!-;wk*_{zyFmyP(D6@JtmKSJYa&}m=xil3*T_Qz z%%Eazr#Y{}>Kf+*%-?VM*U-WElz>&G0Q0>BlhY6pm!m1%=ANURf1X`3>^*p{jK$|D z%q(c5F!Qx6{nyn}m_BfH+URWD5qMo|m_| zU(_MiTDY3s`M0kBI~L%4HSYGGl{iK!gGR8=s=uD0l66`is2fMSNdjw}N6V1-S z9K78F(~sRcsBD|RnP3(`zJ`)WvCHfbavgcV-s9aZUO*lr01l$Iy@ z%#i-&-(w{kU@?WqOZ0UtG*w3dZ-f@fItSQL0j4PIhBlv4>@q@cZ*Qj#5FKM}RxBrn zsm5?X-uX+f`Va5Vzpz$(YvV;6u4D8Cv^ssA9f{*%7| z*jfOSK#XAlL=Cb2Ur~rMW-Ro;l|%htuB?^V%FP=cC9L_j%0fay42Ejv4{N*uK(-1K z^R*Jo+bQ``7eJW+rksV8PDE|48>iw{<7)Fq5b=d??tO2<36KcpK6@^Tu=oXf_xhunN?IO0iNEGv z??xOn;9lB+fb5qkDO8cAu=?3jBi;^O5Fht-W$Jp(N5EQJUbms_EJ(^_OO#M}mZ;7| zW!R_#;sC0vs@NqZ&1z|=?gZelcpt67puFV)46vrepwB?-_#G{!*J`R!`5;D-aO^URoP|apKw; zuk`9b#8#1V_MvT6@24;#3a8br2`{Bu=j8`agAXDwz`E9p163z<<-3b!mE*0dmG8OA zu`~vMdapPGDV4JxYx~Yi2P#BKtrE0(Eb>;M;GuJ1fKMTi!Z)H1NDeZzvCRt%Q8UyDhGrP+mX>f87aJ1 z0X8$cW4L8i5Ep~;@6WDkMI?S|m2Fp&182@A211w5G)9Vo+(BJz#PkKah?W{AXM&AL zSfg*D%}1{--TU6_eLaI&_8nt|1SJqSqMP-WJ8kpC>BgvHt@Zh&m1eiwEOmz6lg+m> zWhRBO@j>4x@L+8oQ_Q>7)%L$%a*VYqliUJqde&6l{@B}+1?U(q4Pa@-^Z4^wEki=k zslMs_(XFdbRIJurqBJy7 za{cCXoo&>BRimL(_H=Qw)=d$$ZyWEF0U7;{rPM9Ts1NzQYGD`0_V`HXHoBzJtXi4G z$#YD7jE7CJvEHrhFbObno-goDR!A2o+#DWxaBG!rn58E1MCz^e@iyAXw#jlZQ@>e> z=vb-(R;^O&-yOIgvvj{(Q>8>-eIoM)NZG-B6VnbJ0Ko3DCWB|;YMG}Q&6hNz6rIcU zK^pUJjWhk+7b$FjGbcSwI{^@8W5_O$m4Ff5cx?PiLnkD8betVOwyBaC=@ z?7r@t%yKxKxEfvG%ky);nI8DO2e?9pyc5<^IM>Q5TN&YaR8BlKD)bQAXqT@GY_q>Y+ z-UxH)K9Q4vOC=L6A#KZ>huPp@tr#DpLr}oQ7cg={Au81SH9pTbo7f~IJ0fU|?#RjS&RE|l|Co869{ngeV#2{^f5BSrAW(b1q2ghA z=73ehy=7Am^Cl>rh&vzeB=Vuab;z{T3pyOs+XaKoF`A&%>h&#$$fE}&e#JmQvjfmM%x3%uB5jfsD3+Ge>) zTE3haYd>*B3Ts%VrkFMo?|Q|O zeZSrBr;86?L@BJI~+5XK66Lcm;vx#3ss^at>s@hZ8>5 zmuGKyMb3~&joYW{6!f@zPZPk9*dIX=nCZ$+ogZF?Ac8i7(gZc?80Gj@m zBiV;kt=C!6dkUXySbofNU=agB7tIrgi);yPtg>F=;o>|m_U7Gf>6fmv75RGUr?c*> z+qc2?gq7Nk{Cp^zbunb$rBgL90gEJpoXhZCb_qLw2n zgd;CkEDKb^#XT9yO`Vgke(M;5>G#I6Jewb$HnSC3A&OHH3^1s%0M5 zD2+x$axtQa zz**w+8hj*fPwPj=FBqRbhJCztj}F9)P7RuWpgU@Eoj-wA&b6S8j3eT@vRA0%Y8{jN z5^t*v3gYn@GFt8>EkH;A-dp$qXB z?I&(_q)~bHn-;0LR4v|>^t=x&veu@Re1Y+xiF<+h|9l)wF(Ij#z&Fq(`Hiif)$*kB zv>*21bPN`WacFrSHqN=$yXiguc)X=PIGB}S=7IxTj>8qCD7U0 zOuK=u-b4(=f$!nzcrAiu{QgVy%(|fTxml?w~sRAu)2l@!z`nH7cV?p)E z_o~YWRPc%sufY2^mxmb1v8Gr4DKC z=quSr^*by~RjRm)nYAlioBs5n_2rugk_o{(=#D*Fs5q5dv%j@bM~>xv&GaulZnHZ# zXkn)Fu2>{#LxKkHzXa`Ej#p$Ke39BePa#V3{z+I-(}Rz)3UOm9tM`8I`s>%vk-fun z*Dd<{oZueQvL^y&MPg{}qoR^J+t+I#ECSeZ*WR5jV{;o6#cRD5QvQ-Ikn7ccxJRDt zc$$|+P*$F~E8E!f=)I2|&&dmgs>;S`>@@M<0jE=U63rJ$`82|sz~$E!(#7tY>S-7+ z-(K4qc^B?4uG4LjHs##G?r+@9x!sMmTbA+q7QhqCT)6?$FCroZh*u~-ultK^-dHch zHf+}cegC<;XM$0&UcMf7T9w>=t&5oip^!cE+WcZCy;krqxt?vO+DuJ49@vB>ujEWf zZhB0iI`|=ypMm^)873Xg)G$L?DXNIH>`^XJz`(ypW%gfwnP|;Oj3w;T=rx{P> zh#}kGLaun5RWYPl54^}aQ!^ZNv%lAC0u&^&=MD|R_jF#!MRov)@+OEMrI5nF0*EG_ zq5vpW`pIN}!TD-kp2G+}G@<(s4VUylkcRz8iVsC&H(rN!Mw4T*>)F-Q#Y?NrJ@wQ$ zax-hbvqR142EWR2i4)l3CH<{yWyK~IbCtzjfh(}xmx?5N=>H8h3f^Y+)n_yYG`g@kH{;_*#~#ek_6Z1>Xsq6OCi-9@ z4Ye-h-M{AX;iYiiy6XV+?-Tx)rzD}G#0NZ_pPPK;!cuhfXEoU$eC*_#`EKF#Ab3fR zEiV5q{A0z`k^59k_90eejE;Oe^3-31bO_vToS|J)fy@Q_WeQ*s<5r7GP;o=bi`6rH zQh6s4rkAbkx8UE?J1BvbwyN^Z(&fdSn=_8Pcr>%AN&IGvQxMwFie=}nHZ_S$inQBb zfoxx>h4wH|zWYfN&W-*o@E-G-b@YE`HoAqtdz@i}SXO0WUPZ(x1(nya%W_W*>ccnh z68IYGOoiH2I=v@_cSsXTIG>Iv>ZqQW9!%B!;UTJF-`HE2zyUBA*tFHOoeKRGd!t~nyop`?p={G~3Oa zFH(!Y)fP{+u6=i!y*Os&g)&I+j;MBNWA#h=ugod!c-RF7vx*JufJA1_9OlL-8;&j1 zcjnrjD@>?d?d&ICdmPxG#fAAE=);}I41ZKnQbxrh4@Z}}KapSKF}KxYiGABj&cAg` zq^@{JZRAYpIb2EZfT)|;+>-oWY@H)6Q8by-yBeEpG?y! zg*DO+PoBL0?$eq@zJFI(QZm=1u}!$z!I(**>_+hX%eigZZ0v(cxy@l8b!hsd>Zz4# zWBz=-q`R%`E<#@+o#KbQHZ>TWuL7?F)o`bNP zwnG6sR6X&@w-XZZ+Jt(=;1m2X<~ZXDk#f$Khf)vhvROt7%eax|fd2LNRJ5?=^&~Q$ zF*~rdq0BdE^9$$A8GNLl-ounnWGd3cbY#3H3#Ucsiw2zemqV7y-+_NhF9f=wU**;w zyEx~L?bum3RWodH5(UBTB>_f`q1cmauz;z9OV=apM(gP+L)qPJuZgy@QV>yBaO_ri zc38w;?<6r$KaJ0~TJ2RR)eM*C$w!^zji2-!1YO1b4(9_S8jrm6AL-FU*Q<^3mgo~{~b6W{?&;J zRdsRdXscp^OU`YMKo~VlJm#2{gl`VS{?<=wWvyr%@Kx_#x34buwGxmqo_id%aV^a~ z;6v0xUXIV}y9+u&w~9wz*DK(pS%z!<;pJ7nI2JyAKNaka5>46oohOfjuXgq3eo5rC zc~&C>)Y6^*%*o7!J_2_J+>t{ckfx(0nY-axQ?I-dlXf@3hs{TQlSqWff+q40#U41+DtMN+1EBw9ges84K@4 zO>U1?+Havh`|-CvMhQ4eIteIm>}26bXO>k^PPfd)E9`Ync4kmhqI}f#(#yogC;OMS zE2uWdKU@1GeJH(Q({frKeC}0pc5Iv+u3PPKoX60%J2ex$diuiYmc`0Ae0F9>g9VE> z*dUO)U6i`WzhhF)OxXR+wY>#t8W-udBCu-9YP-}C1G5PY4na{s@W#+ef7QhwQM>Z8 zGM8gTT^20ObUZGpU!MGZj}G<)eF>jLL$IZaI@TDtBN2tY0B(Pr<^Hg5EO#(l%A_d% zcGlCFxBh?Ly`UV_*jQ{@$bC8KH_Ujx*z8~BH^Jk*?6FC-aE=ZAb>AW&dGa#-r4%Rg zex(XtA_K+wL9f!jkyHgKX((LUkIJn9Bkcqc5s`1Djt#Lx$?KP&Z=g(xjRUSf z+G??lGJpJ4dp*cf#Le`r$;_E%_A%_3L0y#@YW9cmCmwQrk!Rwy9qGV>99KeS zy*caw;Oy6}#O2#n`1zXe>a}IOb8n*P2Ms5eX}yq`w%;8D>oMO)c@^K95VgP@{rbJ) zWY^|{9(XKg+1|zD%XW%uwXQo|lHWYd{nOVf`$t5)b{kJNF7Er|>j?=TZ#5{yvo6m0 zZRi^-7C*6z*~2 zP;!D<{TCW4uFMLDtKFtFg*5!GnMVSV!#7Yzun)QS4J_NYTCZ8Ow zd=U2R0>+*M&9rES`!@WEjUsP*rFMS(v>!)VzZJ-s@E{aJT6v^t4G6cZBvNmFRo$`* z8pYMV3g7pY41y^w+P_cAC8x;N{@PBL8xv=i<rx3iU~!@C;w1Gp zh$ryc7!gz*dDK3(Dt+ZM4OYSTWnVUX+*5y=RzsikbJT$ z^u5-f^;ZyJ-ob$+{*ucKtT7G)x87!}hiw+;qpJF&AAbtYPa;HJK2vV$Lpj4P zPGwJGE>*kggAe2{KcFpTMA&aJZ!sg`G5*6fG?FnXn8b68-3iuIH#l$C`YHQ8RY3E{r4QET<;O{p71@%7r_mJa zA!JNKN@A_Q4rPaCWPKeJAoC)(BJ z+um9Z*}l-MB-n_DSg9|%e@;FBNCririjx++X$wLQ)t@R$gE3aP?N3j2*rmwT(h|&<8`W zK*ZCVRdge=oQd-7pGSf{PP+y;6F|nx26U{Vy=F-Hd*#2Ym~a&+F_Ab=?%?r7xzj37 z05)OV!bSyF++DNh`oyql4ZF6dEd-hl>2daGiA%<>*x1X2U|QX1N4g^yDrMP^W_o7z*^Ej4GPXW_LI)K%D$pp#)HwnE@4U$m&a}0Ef zl$hdN15vnG!&N+3{geaw68~`=ze4!x1?Ya(C@`4&r5O>y(%{#-0U`OHV`|mBaLhO# z2BLi1tC1S*Rk+@OJooS8cM{kf>_DV`6Qsbv$*~8bZ;lMb$NzDBvzvmO1@qbAAd2`0 zUl}EeuY(~L0F?hZ=1!C&A_A|D54;Nh{p$b6)jAOTf}{s_zki({f^3><%E}R1Sz_8l z(x>Jua2iCqJ_R;Z;|1PMB*F-yuc1r`yCYvkj~fdtAV%$ZQUK89HRJSN-PgA^2*8C8 zr)-lx1NyrFkd4z`;g!fb^n)2Oz~zTCi1+1u8CC7gL4ydLS%GIs<>^udS^;1QPo- z4Vlw9Z%k6x`#u}wSp8pbX|^BYYPTN}+_eb~*|u#H8JP3Xf1o|tn0oGAT6&XOscTG{nGMCbmR$0_h9Rb zR15qG3@>YsdgG4B&7_ck05V3k3RFnN0bk%tfXX~Y*<$2qzpBk?i1QLNfKq2MaarTL(b5#Kxj z0NH*_t`0y~NPXSi2sl}5og@N$d7R4Rau6(Baye!t@G(LkPG*%K%OEjHE4~Bcb!n-F zN)lr>T-We3*AI|nuCIDnhBr6(qNI{h$SxX#m0b_1w&(f*vd4ZNcr6U_2U`lB(pTBD zp#U1eA81ks(upIFDX#0F_GwywuvXCjUpJ3c#^n5~7swTtdtNJ>>e^4<7fJ;r2K!m~2EK z{q8`9pdtV&r)sn9&|u-tXoKd3`2C!K@g5mdLK+cxYol|JT;m&n!0MvJOQ#5nj{ziM zKwNUVioy>-f-uRU?^PRv6x6s_yf}}pPjMdNXaJ>x(K}o#O2;O!eObcKY{bCu%bc- zpN!EpI0R$@>GIo_)R`ui%d-W8KV|%CdHn(paMSfmKl2BOX?-gzfeSaqx*c3hdQD8$ zk-vg4@R@e#h<%0|MI-=idGx8rRtgxbr?Uy^O4o<7UD9j>T8{M30ljG8u>Ln=jOFK4 zi|w6TShzu$XK+uBk7rDmf;=l-;~>9q3KFET02vL_;U0!}l;oI_@~iJO+W7(~0=0gG zP#X4wcV45)H$K~^TRd_G12@*)*bsnfcg-fz+7;#&6nmx%0?|@AC5Y zPssTF$irmJ1}=D3HkEj_2IJCN_8%Y;GJ2uEL>M|+v@g{HMAT~_Z8ttC7VZNC=FxIknU_GjTT94{{CA(C2A_qS zODIxHFx? z-_8knt_LRK{CJTorGK4cd4Or(V_YQs-@P2vvBE8PPm!6w%~=YCYoaQ}BjEdRQhoKP7Uf4-=F)&1xE z2Peq8rJ}6}iivp~c`T@oSII?S&;R#)N&A2GBVZo)-!J~(HU9rkQ~yI?x}+ujTkxhm P1pKHg>nK$yz(fBB2yYb~ literal 0 HcmV?d00001 From d4d497280705d543a83d25ccbed18a05b06bfe82 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 30 Jan 2026 13:23:04 -0700 Subject: [PATCH 25/26] Update baseline image per PR changes --- test/image/baselines/text_on_shapes_basic.png | Bin 71230 -> 71428 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/text_on_shapes_basic.png b/test/image/baselines/text_on_shapes_basic.png index ec1e63dd28833376bcf33480795509746b19de08..519474824b78e269f6db7a931801a86c4bd3b542 100644 GIT binary patch delta 14890 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 Date: Fri, 30 Jan 2026 13:24:03 -0700 Subject: [PATCH 26/26] Update draftlog --- draftlogs/7673_add.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draftlogs/7673_add.md b/draftlogs/7673_add.md index d76f9595d12..04d803b98c6 100644 --- a/draftlogs/7673_add.md +++ b/draftlogs/7673_add.md @@ -1 +1 @@ - - Add support for dashed marker lines in scatter plots [[#7673](https://github.com/plotly/plotly.js/pull/7673)] + - Add support for dashed marker lines in scatter plots [[#7673](https://github.com/plotly/plotly.js/pull/7673)], with thanks to @chrimaho for the contribution!