Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
de99bdc
Add support for dashed marker lines in scatter plots
chrimaho Dec 12, 2025
8c1fdf2
Add changelog entry for dashed marker line support
chrimaho Dec 12, 2025
d838891
Add dash attribute support to marker lines across scatter trace types
chrimaho Dec 12, 2025
0df71bb
Hide marker line for blank points in scatter plots
chrimaho Dec 13, 2025
26eecd9
Add dash style property to marker line schemas
chrimaho Dec 13, 2025
6ce3af4
Fix failing CI for `text_on_shapes_basic` pixel comparison
chrimaho Dec 13, 2025
753c6ed
Simplify stroke styling and remove unused dash line logic
chrimaho Dec 13, 2025
6de355d
update plot-schema diff
chrimaho Dec 14, 2025
8573067
Revert `plot-schema` to version from `master` branch
chrimaho Dec 20, 2025
6fa75bc
Remove `arrayOk` attribute from the `marker.line` attribute of the `s…
chrimaho Dec 20, 2025
ac01dc3
Remove `marker.line.dash` changes from the `scatter3d`, `scattergl`, …
chrimaho Dec 20, 2025
f348454
Implement logic to actually draw the dashed lines
chrimaho Dec 20, 2025
f733505
Fix missing end of file newline
chrimaho Jan 18, 2026
3dd16f7
Add `arrayOk` attribute to `marker.line.dash` for scatter plots.
chrimaho Jan 18, 2026
7eb1943
Add tests and mock data for scatter marker line dash support
chrimaho Jan 18, 2026
90ab105
Update `plot-schema` diff
chrimaho Jan 18, 2026
e8a702d
Rename test file
camdecoster Jan 29, 2026
cddc465
Update mock layout
camdecoster Jan 29, 2026
67458e7
Linting/formatting
camdecoster Jan 30, 2026
65a0d87
Don't coerce marker.line.dash for some traces
camdecoster Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/7673_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add support for dashed marker lines in scatter plots [[#7673](https://github.com/plotly/plotly.js/pull/7673)]
3 changes: 3 additions & 0 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/traces/scatter/arrays_to_calcdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/traces/scatter/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,9 @@ module.exports = {
anim: true,
description: 'Sets the width (in px) of the lines bounding the marker points.'
},
dash: extendFlat({}, dash, {
arrayOk: true
}),
editType: 'calc'
},
colorScaleAttrs('marker.line', { anim: true })
Expand Down
47 changes: 21 additions & 26 deletions src/traces/scatter/marker_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,70 +12,65 @@ 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;
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) {
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);
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 defaultMLC = Color.defaultLine;
} 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);
if (!opts.noLineDash) 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') {
coerce('marker.gradient.color');
}
if (gradientType !== 'none') coerce('marker.gradient.color');
}
};
6 changes: 5 additions & 1 deletion src/traces/scatter3d/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions src/traces/scattercarpet/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ module.exports = {
line: extendFlat(
{
width: scatterMarkerLineAttrs.width,
dash: scatterMarkerLineAttrs.dash,
editType: 'calc'
},
colorScaleAttrs('marker.line')
Expand Down
3 changes: 2 additions & 1 deletion src/traces/scattergeo/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ module.exports = overrideAll(
colorbar: scatterMarkerAttrs.colorbar,
line: extendFlat(
{
width: scatterMarkerLineAttrs.width
width: scatterMarkerLineAttrs.width,
dash: scatterMarkerLineAttrs.dash
},
colorAttributes('marker.line')
),
Expand Down
6 changes: 5 additions & 1 deletion src/traces/scattergl/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
6 changes: 5 additions & 1 deletion src/traces/scatterpolargl/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions src/traces/scatterternary/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ module.exports = {
line: extendFlat(
{
width: scatterMarkerLineAttrs.width,
dash: scatterMarkerLineAttrs.dash,
editType: 'calc'
},
colorScaleAttrs('marker.line')
Expand Down
6 changes: 5 additions & 1 deletion src/traces/splom/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
82 changes: 82 additions & 0 deletions test/image/mocks/zz-scatter_marker_line_dash.json
Original file line number Diff line number Diff line change
@@ -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, 5]
},
"width": 600,
"height": 400
}
}
Loading