diff --git a/draftlogs/8135_add.md b/draftlogs/8135_add.md new file mode 100644 index 00000000000..1e61e524a07 --- /dev/null +++ b/draftlogs/8135_add.md @@ -0,0 +1 @@ + - Add `line.dash` to `scattermap` traces to draw dashed and dotted lines on maps [[#8135](https://github.com/plotly/plotly.js/pull/8135)] diff --git a/src/traces/scattermap/attributes.js b/src/traces/scattermap/attributes.js index 29c0cfcbb8a..987a35001af 100644 --- a/src/traces/scattermap/attributes.js +++ b/src/traces/scattermap/attributes.js @@ -99,10 +99,8 @@ module.exports = overrideAll( line: { color: lineAttrs.color, - width: lineAttrs.width - - // TODO - // dash: dash + width: lineAttrs.width, + dash: lineAttrs.dash }, connectgaps: scatterAttrs.connectgaps, diff --git a/src/traces/scattermap/convert.js b/src/traces/scattermap/convert.js index a4c35683809..af07ae5550f 100644 --- a/src/traces/scattermap/convert.js +++ b/src/traces/scattermap/convert.js @@ -69,7 +69,19 @@ module.exports = function convert(gd, calcTrace) { 'line-opacity': trace.opacity }); - // TODO convert line.dash into line-dasharray + // MapLibre scales line-dasharray lengths by the line width, so reuse + // the px dash lengths from Drawing.dashStyle and convert them back + // to line-width units. 'solid' yields an empty string: no dasharray. + const dashPx = Drawing.dashStyle(trace.line.dash, trace.line.width); + if(dashPx) { + const w = trace.line.width || 1; + const dashArray = dashPx.split(/[\s,]+/) + .map(s => parseFloat(s) / w) + .filter(v => isFinite(v) && v >= 0); + if(dashArray.length) { + line.paint['line-dasharray'] = dashArray; + } + } } if (hasCircles) { diff --git a/src/traces/scattermap/defaults.js b/src/traces/scattermap/defaults.js index faeb083ac08..521d0807e7d 100644 --- a/src/traces/scattermap/defaults.js +++ b/src/traces/scattermap/defaults.js @@ -49,7 +49,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } if (subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noDash: true }); + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); coerce('connectgaps'); } diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index 8a855665e73..219488f703c 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -8774,7 +8774,21 @@ export interface ScattermapData { * Minimum: 0 */ legendwidth?: number; - line?: _internal.ErrorY; + line?: { + /** Sets the line color. */ + color?: Color; + /** + * 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*). + * @default 'solid' + */ + dash?: Dash; + /** + * Sets the line width (in px). + * @default 2 + * Minimum: 0 + */ + width?: number; + }; /** Sets the longitude coordinates (in degrees East). */ lon?: Datum[] | Datum[][] | TypedArray; marker?: { diff --git a/test/image/mocks/map_line-dash.json b/test/image/mocks/map_line-dash.json new file mode 100644 index 00000000000..7891419965a --- /dev/null +++ b/test/image/mocks/map_line-dash.json @@ -0,0 +1,38 @@ +{ + "data": [ + { + "lat": [30, 20, 10, 20], + "line": { "width": 4 }, + "lon": [10, 20, 30, 40], + "mode": "lines", + "name": "solid", + "type": "scattermap" + }, + { + "lat": [20, 10, 0, 10], + "line": { "dash": "dash", "width": 4 }, + "lon": [10, 20, 30, 40], + "mode": "lines", + "name": "dash", + "type": "scattermap" + }, + { + "lat": [10, 0, -10, 0], + "line": { "dash": "dot", "width": 4 }, + "lon": [10, 20, 30, 40], + "mode": "lines", + "name": "dot", + "type": "scattermap" + } + ], + "layout": { + "autosize": true, + "height": 450, + "map": { + "center": { "lat": 10, "lon": 25 }, + "style": "streets", + "zoom": 1.5 + }, + "width": 700 + } +} diff --git a/test/jasmine/tests/scattermap_test.js b/test/jasmine/tests/scattermap_test.js index 9d49db1951d..c0c086c912d 100644 --- a/test/jasmine/tests/scattermap_test.js +++ b/test/jasmine/tests/scattermap_test.js @@ -123,6 +123,25 @@ describe('scattermap defaults', function() { expect(fullTrace.marker).toBeDefined(); expect(fullTrace.marker.line).toBeUndefined(); }); + + it('should default \'line.dash\' to \'solid\' and coerce named dash styles', function() { + const solidTrace = _supply({ + mode: 'lines', + lon: [1, 2, 3], + lat: [2, 3, 3] + }); + + expect(solidTrace.line.dash).toBe('solid', 'default dash'); + + const dashedTrace = _supply({ + mode: 'lines', + lon: [1, 2, 3], + lat: [2, 3, 3], + line: {dash: 'dashdot'} + }); + + expect(dashedTrace.line.dash).toBe('dashdot', 'coerced dash'); + }); }); describe('scattermap convert', function() { @@ -592,6 +611,29 @@ describe('scattermap convert', function() { expect(opts.line.geojson.coordinates).toEqual(lineCoords, 'have correct line coords'); }); + it('should convert \'line.dash\' to a MapLibre \'line-dasharray\' paint value', function() { + const dasharrayOf = (line) => { + const opts = _convert(Lib.extendFlat({}, base, { + mode: 'lines', + line: line + })); + return opts.line.paint['line-dasharray']; + }; + + // 'solid' (the default) leaves line-dasharray unset + expect(dasharrayOf({})).toBe(undefined, 'default solid'); + expect(dasharrayOf({dash: 'solid'})).toBe(undefined, 'explicit solid'); + + // named styles mirror the SVG dash lengths from Drawing.dashStyle, + // expressed in line-width units (MapLibre scales them by line width) + expect(dasharrayOf({dash: 'dash', width: 2})).toEqual([4.5, 4.5], 'dash style'); + expect(dasharrayOf({dash: 'dot', width: 4})).toEqual([1, 1], 'dot style'); + expect(dasharrayOf({dash: 'longdashdot', width: 2})).toEqual([7.5, 3, 1.5, 3], 'longdashdot style'); + + // custom px dash lists are honored, converted to line-width units + expect(dasharrayOf({dash: '5px,10px,2px,2px', width: 2})).toEqual([2.5, 5, 1, 1], 'custom px list'); + }); + it('should correctly convert \'textposition\' to \'text-anchor\' and \'text-offset\'', function() { var specs = { 'top left': ['top-right', [-0.65, -1.65]], diff --git a/test/plot-schema.json b/test/plot-schema.json index 4d51312e82d..d8ba747b79d 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -64331,6 +64331,20 @@ "editType": "calc", "valType": "color" }, + "dash": { + "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", "role": "object", "width": {