diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index d83f4eb04dd..a350ca54d78 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -87,6 +87,23 @@ module.exports = overrideAll({ 'are only available for *circle* symbols.' ].join(' ') }, + angle: { + valType: 'number', + dflt: 0, + role: 'style', + arrayOk: true, + description: [ + 'Sets the marker rotation, in degrees clockwise.' + ].join(' ') + }, + allowoverlap: { + valType: 'boolean', + dflt: false, + role: 'style', + description: [ + 'Flag to draw all symbols, even if they overlap.' + ].join(' ') + }, opacity: markerAttrs.opacity, size: markerAttrs.size, sizeref: markerAttrs.sizeref, diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index 0fabf942327..abb4d9963aa 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -104,6 +104,19 @@ module.exports = function convert(gd, calcTrace) { 'icon-size': trace.marker.size / 10 }); + if('angle' in trace.marker) { + Lib.extendFlat(symbol.layout, { + // unfortunately cant use {angle} do to this issue: + // https://github.com/mapbox/mapbox-gl-js/issues/873 + 'icon-rotate': { + type: 'identity', property: 'angle' + }, + 'icon-rotation-alignment': 'map' + }); + } + + symbol.layout['icon-allow-overlap'] = trace.marker.allowoverlap; + Lib.extendFlat(symbol.paint, { 'icon-opacity': trace.opacity * trace.marker.opacity, @@ -239,15 +252,21 @@ function makeSymbolGeoJSON(calcTrace, gd) { var marker = trace.marker || {}; var symbol = marker.symbol; + var angle = marker.angle; var fillSymbol = (symbol !== 'circle') ? getFillFunc(symbol) : blankFillFunc; + var fillAngle = (angle) ? + getFillFunc(angle, true) : + blankFillFunc; + var fillText = subTypes.hasText(trace) ? getFillFunc(trace.text) : blankFillFunc; + var features = []; for(var i = 0; i < calcTrace.length; i++) { @@ -266,7 +285,7 @@ function makeSymbolGeoJSON(calcTrace, gd) { var meta = trace._meta || {}; text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta); } else { - text = fillText(calcPt.tx); + text = fillText(i); } if(text) { @@ -280,7 +299,8 @@ function makeSymbolGeoJSON(calcTrace, gd) { coordinates: calcPt.lonlat }, properties: { - symbol: fillSymbol(calcPt.mx), + symbol: fillSymbol(i), + angle: fillAngle(i), text: text } }); @@ -292,9 +312,12 @@ function makeSymbolGeoJSON(calcTrace, gd) { }; } -function getFillFunc(attr) { +function getFillFunc(attr, numeric) { if(Lib.isArrayOrTypedArray(attr)) { - return function(v) { return v; }; + if(numeric) { + return function(i) { return isNumeric(attr[i]) ? +attr[i] : 0; }; + } + return function(i) { return attr[i]; }; } else if(attr) { return function() { return attr; }; } else { diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index 628b80cb2f5..21842d9abc7 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -43,6 +43,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(subTypes.hasMarkers(traceOut)) { handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noLine: true}); + coerce('marker.allowoverlap'); + coerce('marker.angle'); + // array marker.size and marker.color are only supported with circles var marker = traceOut.marker; if(marker.symbol !== 'circle') { diff --git a/test/image/baselines/mapbox_angles.png b/test/image/baselines/mapbox_angles.png index 3c1d948523f..1f9628c3b1b 100644 Binary files a/test/image/baselines/mapbox_angles.png and b/test/image/baselines/mapbox_angles.png differ diff --git a/test/image/mocks/mapbox_angles.json b/test/image/mocks/mapbox_angles.json index 8592525ae5e..e3243dd10fd 100644 --- a/test/image/mocks/mapbox_angles.json +++ b/test/image/mocks/mapbox_angles.json @@ -41,7 +41,9 @@ "monument", "harbor", "music" - ] + ], + "angle": [-45, 45, 0], + "allowoverlap":true }, "subplot": "mapbox2" } diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 35691693625..298d64f7b98 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -465,6 +465,30 @@ describe('scattermapbox convert', function() { expect(symbolProps).toEqual(expected, 'geojson properties'); }); + + it('should allow symbols to be rotated and overlapped', function() { + var opts = _convert(Lib.extendFlat({}, base, { + mode: 'markers', + marker: { + symbol: ['monument', 'music', 'harbor'], + angle: [0, 90, 45], + allowoverlap: true + }, + })); + + var symbolAngle = opts.symbol.geojson.features.map(function(f) { + return f.properties.angle; + }); + + var expected = [0, 90, 45, 0, 0]; + expect(symbolAngle).toEqual(expected, 'geojson properties'); + + + expect(opts.symbol.layout['icon-rotate'].property).toEqual('angle', 'symbol.layout.icon-rotate'); + expect(opts.symbol.layout['icon-allow-overlap']).toEqual(true, 'symbol.layout.icon-allow-overlap'); + }); + + it('should generate correct output for text + lines traces', function() { var opts = _convert(Lib.extendFlat({}, base, { mode: 'lines+text',