diff --git a/draftlogs/7825_update.md b/draftlogs/7825_update.md new file mode 100644 index 00000000000..a74243ff3ef --- /dev/null +++ b/draftlogs/7825_update.md @@ -0,0 +1 @@ +- Enable scattermap icons to render in color, upgrade Maki icons version to 8.2, and standardize scattermap legend icons to circles [[#7825](https://github.com/plotly/plotly.js/pull/7825)] diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 861df3131a5..970fc2e056d 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -1866,7 +1866,10 @@ function getMarkerAngle(d, trace) { if (angle === undefined) { angle = trace.marker.angle; - if (!angle || Lib.isArrayOrTypedArray(angle)) { + // For scattermap traces, `trace.marker.angle` defaults to 'auto', + // which is meaningful for the MapLibre code but not for plotly.js itself. + // Therefore we need to coerce any non-numeric values to 0 (no rotation). + if (!isNumeric(angle) || Lib.isArrayOrTypedArray(angle)) { angle = 0; } } diff --git a/src/components/legend/style.js b/src/components/legend/style.js index d2b3350c0c8..a2a45e9ea5a 100644 --- a/src/components/legend/style.js +++ b/src/components/legend/style.js @@ -207,7 +207,12 @@ module.exports = function style(s, gd, legend) { if (showMarker) { dEdit.mc = boundVal('marker.color', pickFirst); - dEdit.mx = boundVal('marker.symbol', pickFirst); + // Scattermap traces use marker.symbol to specify the Maki icon used in + // the map itself, which usually doesn't correspond to a valid + // Plotly symbol. Always draw a circle so the swatch is consistent + // across symbols rather than silently mismatched. + var isScattermapTrace = trace.type === 'scattermap' || trace.type === 'scattermapbox'; + dEdit.mx = isScattermapTrace ? 'circle' : boundVal('marker.symbol', pickFirst); 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); diff --git a/src/plots/map/layers.js b/src/plots/map/layers.js index be32c2dc21b..ca4b4aa7b26 100644 --- a/src/plots/map/layers.js +++ b/src/plots/map/layers.js @@ -230,7 +230,7 @@ function convertOpts(opts) { var textOpts = convertTextOpts(symbol.textposition, symbol.iconsize); Lib.extendFlat(layout, { - 'icon-image': symbol.icon + '-15', + 'icon-image': symbol.icon, 'icon-size': symbol.iconsize / 10, 'text-field': symbol.text, diff --git a/src/plots/map/map.js b/src/plots/map/map.js index 9a397743482..6c377d456d1 100644 --- a/src/plots/map/map.js +++ b/src/plots/map/map.js @@ -107,14 +107,14 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { var requestedIcons = {}; map.on('styleimagemissing', function(e) { var id = e.id; - if(!requestedIcons[id] && id.includes('-15')) { + if(!requestedIcons[id] && /^[a-zA-Z0-9-]+$/.test(id)) { requestedIcons[id] = true; var img = new Image(15, 15); img.onload = function() { - map.addImage(id, img); + map.addImage(id, img, {sdf: true}); }; img.crossOrigin = 'Anonymous'; - img.src = 'https://unpkg.com/maki@2.1.0/icons/' + id + '.svg'; + img.src = "https://cdn.jsdelivr.net/npm/@mapbox/maki@8.2.0/icons/" + id + '.svg'; } }); diff --git a/src/traces/scattermap/convert.js b/src/traces/scattermap/convert.js index c2bfce0c56d..a4c35683809 100644 --- a/src/traces/scattermap/convert.js +++ b/src/traces/scattermap/convert.js @@ -116,7 +116,7 @@ module.exports = function convert(gd, calcTrace) { Lib.extendFlat(symbol.layout, { visibility: 'visible', - 'icon-image': '{symbol}-15', + 'icon-image': '{symbol}', 'text-field': '{text}' }); @@ -141,9 +141,11 @@ module.exports = function convert(gd, calcTrace) { Lib.extendFlat(symbol.paint, { 'icon-opacity': trace.opacity * trace.marker.opacity, - - // TODO does not work ?? - 'icon-color': trace.marker.color + 'icon-color': trace.marker.color, + // Set a tiny blur to make the edges look nicer + 'icon-halo-color': trace.marker.color, + 'icon-halo-width': 2, + 'icon-halo-blur': 1.5, }); } diff --git a/test/image/baselines/map_symbol-text.png b/test/image/baselines/map_symbol-text.png index 9b8d497ae32..ba81a0652db 100644 Binary files a/test/image/baselines/map_symbol-text.png and b/test/image/baselines/map_symbol-text.png differ diff --git a/test/jasmine/tests/scattermap_test.js b/test/jasmine/tests/scattermap_test.js index e4010848e30..893fbef561e 100644 --- a/test/jasmine/tests/scattermap_test.js +++ b/test/jasmine/tests/scattermap_test.js @@ -1248,6 +1248,45 @@ describe('Test plotly events on a scattermap plot when css transform is present: }); }); +describe('scattermap legend', function() { + var gd; + + beforeEach(function() { + Plotly.setPlotConfig({}); + gd = createGraphDiv(); + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + it('@gl should always draw a circle swatch regardless of marker.symbol', function(done) { + // marker.symbol on map traces is a Maki/sprite icon name that the SVG + // legend can't reproduce, so the swatch should consistently be a circle. + // The circle symbol path is the only one using an arc ('A') command; + // square/triangle/etc. use only straight (H/V/L) segments. + Plotly.newPlot(gd, { + data: [ + { type: 'scattermap', lat: [0], lon: [0], mode: 'markers', name: 'square', marker: { symbol: 'square' } }, + { type: 'scattermap', lat: [1], lon: [1], mode: 'markers', name: 'tri', marker: { symbol: 'triangle-stroked' } } + ], + layout: { + map: { style: 'open-street-map', zoom: 6, center: { lat: 0.5, lon: 0.5 } }, + showlegend: true + } + }).then(function() { + var swatches = gd.querySelectorAll('.legend .legendpoints path.scatterpts'); + expect(swatches.length).toBe(2, 'one swatch per trace'); + swatches.forEach(function(node) { + var d = node.getAttribute('d'); + expect(d.indexOf('A')).toBeGreaterThan(-1, 'swatch is a circle (has arc): ' + d); + expect(d.indexOf('NaN')).toBe(-1, 'swatch path has no NaN: ' + d); + }); + }).then(done, done.fail); + }); +}); + describe('scattermap restyle', function() { var gd;