diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 3bd4875456b..1548a2400a6 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -194,7 +194,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) { var isSizeConstrained = options.width || options.height; - var annTextClip = fullLayout._defs.select('.clips') + var annTextClip = fullLayout._topclips .selectAll('#' + annClipID) .data(isSizeConstrained ? [0] : []); diff --git a/src/components/fx/layout_defaults.js b/src/components/fx/layout_defaults.js index a4800d094f0..f0939835d49 100644 --- a/src/components/fx/layout_defaults.js +++ b/src/components/fx/layout_defaults.js @@ -29,11 +29,17 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { coerce('hovermode', hovermodeDflt); - // if only mapbox subplots is present on graph, + // if only mapbox or geo subplots is present on graph, // reset 'zoom' dragmode to 'pan' until 'zoom' is implemented, // so that the correct modebar button is active - if(layoutOut._has('mapbox') && layoutOut._basePlotModules.length === 1 && - layoutOut.dragmode === 'zoom') { + var hasMapbox = layoutOut._has('mapbox'); + var hasGeo = layoutOut._has('geo'); + var len = layoutOut._basePlotModules.length; + + if(layoutOut.dragmode === 'zoom' && ( + ((hasMapbox || hasGeo) && len === 1) || + (hasMapbox && hasGeo && len === 2) + )) { layoutOut.dragmode = 'pan'; } }; diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 2fa852e70de..3f2875c55b7 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -446,23 +446,24 @@ modeBarButtons.hoverClosestGeo = { }; function handleGeo(gd, ev) { - var button = ev.currentTarget, - attr = button.getAttribute('data-attr'), - val = button.getAttribute('data-val') || true, - fullLayout = gd._fullLayout, - geoIds = Plots.getSubplotIds(fullLayout, 'geo'); + var button = ev.currentTarget; + var attr = button.getAttribute('data-attr'); + var val = button.getAttribute('data-val') || true; + var fullLayout = gd._fullLayout; + var geoIds = Plots.getSubplotIds(fullLayout, 'geo'); for(var i = 0; i < geoIds.length; i++) { - var geo = fullLayout[geoIds[i]]._subplot; + var id = geoIds[i]; + var geoLayout = fullLayout[id]; if(attr === 'zoom') { - var scale = geo.projection.scale(); + var scale = geoLayout.projection.scale; var newScale = (val === 'in') ? 2 * scale : 0.5 * scale; - geo.projection.scale(newScale); - geo.zoom.scale(newScale); - geo.render(); + + Plotly.relayout(gd, id + '.projection.scale', newScale); + } else if(attr === 'reset') { + resetView(gd, 'geo'); } - else if(attr === 'reset') geo.zoomReset(); } } @@ -535,8 +536,8 @@ modeBarButtons.resetViews = { button.setAttribute('data-attr', 'resetLastSave'); handleCamera3d(gd, ev); - // N.B handleCamera3d also triggers a replot for - // geo subplots. + resetView(gd, 'geo'); + resetView(gd, 'mapbox'); } }; @@ -581,22 +582,26 @@ modeBarButtons.resetViewMapbox = { attr: 'reset', icon: Icons.home, click: function(gd) { - var fullLayout = gd._fullLayout; - var subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'); - var aObj = {}; - - for(var i = 0; i < subplotIds.length; i++) { - var id = subplotIds[i]; - var subplotObj = fullLayout[id]._subplot; - var viewInitial = subplotObj.viewInitial; - var viewKeys = Object.keys(viewInitial); - - for(var j = 0; j < viewKeys.length; j++) { - var key = viewKeys[j]; - aObj[id + '.' + key] = viewInitial[key]; - } - } - - Plotly.relayout(gd, aObj); + resetView(gd, 'mapbox'); } }; + +function resetView(gd, subplotType) { + var fullLayout = gd._fullLayout; + var subplotIds = Plots.getSubplotIds(fullLayout, subplotType); + var aObj = {}; + + for(var i = 0; i < subplotIds.length; i++) { + var id = subplotIds[i]; + var subplotObj = fullLayout[id]._subplot; + var viewInitial = subplotObj.viewInitial; + var viewKeys = Object.keys(viewInitial); + + for(var j = 0; j < viewKeys.length; j++) { + var key = viewKeys[j]; + aObj[id + '.' + key] = viewInitial[key]; + } + } + + Plotly.relayout(gd, aObj); +} diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 7bc6c3b8b34..8feee485140 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -71,16 +71,16 @@ module.exports = function manageModeBar(gd) { // logic behind which buttons are displayed by default function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { - var fullLayout = gd._fullLayout, - fullData = gd._fullData; + var fullLayout = gd._fullLayout; + var fullData = gd._fullData; - var hasCartesian = fullLayout._has('cartesian'), - hasGL3D = fullLayout._has('gl3d'), - hasGeo = fullLayout._has('geo'), - hasPie = fullLayout._has('pie'), - hasGL2D = fullLayout._has('gl2d'), - hasTernary = fullLayout._has('ternary'), - hasMapbox = fullLayout._has('mapbox'); + var hasCartesian = fullLayout._has('cartesian'); + var hasGL3D = fullLayout._has('gl3d'); + var hasGeo = fullLayout._has('geo'); + var hasPie = fullLayout._has('pie'); + var hasGL2D = fullLayout._has('gl2d'); + var hasTernary = fullLayout._has('ternary'); + var hasMapbox = fullLayout._has('mapbox'); var groups = []; @@ -112,18 +112,13 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { addGroup(['hoverClosest3d']); } - if(hasGeo) { - addGroup(['zoomInGeo', 'zoomOutGeo', 'resetGeo']); - addGroup(['hoverClosestGeo']); - } - var allAxesFixed = areAllAxesFixed(fullLayout), dragModeGroup = []; if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) { dragModeGroup = ['zoom2d', 'pan2d']; } - if(hasMapbox) { + if(hasMapbox || hasGeo) { dragModeGroup = ['pan2d']; } if(isSelectable(fullData)) { @@ -138,18 +133,17 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { if(hasCartesian && hasPie) { addGroup(['toggleHover']); - } - else if(hasGL2D) { + } else if(hasGL2D) { addGroup(['hoverClosestGl2d']); - } - else if(hasCartesian) { + } else if(hasCartesian) { addGroup(['toggleSpikelines', 'hoverClosestCartesian', 'hoverCompareCartesian']); - } - else if(hasPie) { + } else if(hasPie) { addGroup(['hoverClosestPie']); - } - else if(hasMapbox) { + } else if(hasMapbox) { addGroup(['resetViewMapbox', 'toggleHover']); + } else if(hasGeo) { + addGroup(['zoomInGeo', 'zoomOutGeo', 'resetGeo']); + addGroup(['hoverClosestGeo']); } return appendButtonsToGroups(groups, buttonsToAdd); diff --git a/src/lib/array_to_calc_item.js b/src/lib/array_to_calc_item.js deleted file mode 100644 index 4a968234f0a..00000000000 --- a/src/lib/array_to_calc_item.js +++ /dev/null @@ -1,15 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - - -'use strict'; - -// similar to Lib.mergeArray, but using inside a loop -module.exports = function arrayToCalcItem(traceAttr, calcItem, calcAttr, i) { - if(Array.isArray(traceAttr)) calcItem[calcAttr] = traceAttr[i]; -}; diff --git a/src/lib/geojson_utils.js b/src/lib/geojson_utils.js index b123c1c68ba..66dea69f192 100644 --- a/src/lib/geojson_utils.js +++ b/src/lib/geojson_utils.js @@ -54,32 +54,22 @@ exports.calcTraceToLineCoords = function(calcTrace) { * * @param {array} coords * results form calcTraceToLineCoords - * @param {object} trace - * (optional) full trace object to be added on to output - * * @return {object} out * GeoJSON object * */ -exports.makeLine = function(coords, trace) { - var out = {}; - +exports.makeLine = function(coords) { if(coords.length === 1) { - out = { + return { type: 'LineString', coordinates: coords[0] }; - } - else { - out = { + } else { + return { type: 'MultiLineString', coordinates: coords }; } - - if(trace) out.trace = trace; - - return out; }; /** @@ -87,37 +77,27 @@ exports.makeLine = function(coords, trace) { * * @param {array} coords * results form calcTraceToLineCoords - * @param {object} trace - * (optional) full trace object to be added on to output - * * @return {object} out * GeoJSON object */ -exports.makePolygon = function(coords, trace) { - var out = {}; - +exports.makePolygon = function(coords) { if(coords.length === 1) { - out = { + return { type: 'Polygon', coordinates: coords }; - } - else { + } else { var _coords = new Array(coords.length); for(var i = 0; i < coords.length; i++) { _coords[i] = [coords[i]]; } - out = { + return { type: 'MultiPolygon', coordinates: _coords }; } - - if(trace) out.trace = trace; - - return out; }; /** diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 58244368e0e..486aea85e45 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2748,8 +2748,8 @@ Plotly.purge = function purge(gd) { // makePlotFramework: Create the plot container and axes // ------------------------------------------------------- function makePlotFramework(gd) { - var gd3 = d3.select(gd), - fullLayout = gd._fullLayout; + var gd3 = d3.select(gd); + var fullLayout = gd._fullLayout; // Plot container fullLayout._container = gd3.selectAll('.plot-container').data([0]); @@ -2795,9 +2795,15 @@ function makePlotFramework(gd) { fullLayout._defs = fullLayout._paper.append('defs') .attr('id', 'defs-' + fullLayout._uid); + fullLayout._clips = fullLayout._defs.append('g') + .classed('clips', true); + fullLayout._topdefs = fullLayout._toppaper.append('defs') .attr('id', 'topdefs-' + fullLayout._uid); + fullLayout._topclips = fullLayout._topdefs.append('g') + .classed('clips', true); + fullLayout._bgLayer = fullLayout._paper.append('g') .classed('bglayer', true); diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 4e907028c32..f59434c9963 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -151,8 +151,7 @@ exports.lsInner = function(gd) { // Clip so that data only shows up on the plot area. plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot'; - var plotClip = fullLayout._defs.selectAll('g.clips') - .selectAll('#' + plotinfo.clipId) + var plotClip = fullLayout._clips.selectAll('#' + plotinfo.clipId) .data([0]); plotClip.enter().append('clipPath') @@ -490,28 +489,15 @@ exports.doTicksRelayout = function(gd) { exports.doModeBar = function(gd) { var fullLayout = gd._fullLayout; - var subplotIds, subplotObj, i; ModeBar.manage(gd); initInteractions(gd); - subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d'); - for(i = 0; i < subplotIds.length; i++) { - subplotObj = fullLayout[subplotIds[i]]._scene; - subplotObj.updateFx(fullLayout.dragmode, fullLayout.hovermode); + for(var i = 0; i < fullLayout._basePlotModules.length; i++) { + var updateFx = fullLayout._basePlotModules[i].updateFx; + if(updateFx) updateFx(fullLayout); } - subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'); - for(i = 0; i < subplotIds.length; i++) { - subplotObj = fullLayout._plots[subplotIds[i]]._scene2d; - subplotObj.updateFx(fullLayout.dragmode); - } - - subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'); - for(i = 0; i < subplotIds.length; i++) { - subplotObj = fullLayout[subplotIds[i]]._subplot; - subplotObj.updateFx(fullLayout); - } return Plots.previousPromises(gd); }; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7d18cc8fd8a..890f5350e16 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1602,15 +1602,13 @@ axes.findSubplotsWithAxis = function(subplots, ax) { // makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings axes.makeClipPaths = function(gd) { - var fullLayout = gd._fullLayout, - defs = fullLayout._defs, - fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}, - fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}, - xaList = axes.list(gd, 'x', true), - yaList = axes.list(gd, 'y', true), - clipList = [], - i, - j; + var fullLayout = gd._fullLayout; + var fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}; + var fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}; + var xaList = axes.list(gd, 'x', true); + var yaList = axes.list(gd, 'y', true); + var clipList = []; + var i, j; for(i = 0; i < xaList.length; i++) { clipList.push({x: xaList[i], y: fullHeight}); @@ -1620,16 +1618,10 @@ axes.makeClipPaths = function(gd) { } } - var defGroup = defs.selectAll('g.clips') - .data([0]); - - defGroup.enter().append('g') - .classed('clips', true); - // selectors don't work right with camelCase tags, // have to use class instead // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I - var axClips = defGroup.selectAll('.axesclip') + var axClips = fullLayout._clips.selectAll('.axesclip') .data(clipList, function(d) { return d.x._id + d.y._id; }); axClips.enter().append('clipPath') @@ -1649,7 +1641,6 @@ axes.makeClipPaths = function(gd) { }); }; - // doTicks: draw ticks, grids, and tick labels // axid: 'x', 'y', 'x2' etc, // blank to do all, diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index d8542f5c090..50095155a99 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -79,16 +79,18 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { if(!trace._module || !trace._module.selectPoints) continue; if(dragOptions.subplot) { - if(trace.subplot !== dragOptions.subplot) continue; - - searchTraces.push({ - selectPoints: trace._module.selectPoints, - cd: cd, - xaxis: dragOptions.xaxes[0], - yaxis: dragOptions.yaxes[0] - }); - } - else { + if( + trace.subplot === dragOptions.subplot || + trace.geo === dragOptions.subplot + ) { + searchTraces.push({ + selectPoints: trace._module.selectPoints, + cd: cd, + xaxis: dragOptions.xaxes[0], + yaxis: dragOptions.yaxes[0] + }); + } + } else { if(xAxisIds.indexOf(trace.xaxis) === -1) continue; if(yAxisIds.indexOf(trace.yaxis) === -1) continue; diff --git a/src/plots/geo/constants.js b/src/plots/geo/constants.js index fde13b5ef2f..384803057c3 100644 --- a/src/plots/geo/constants.js +++ b/src/plots/geo/constants.js @@ -6,13 +6,10 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; -var params = module.exports = {}; - // projection names to d3 function name -params.projNames = { +exports.projNames = { // d3.geo.projection 'equirectangular': 'equirectangular', 'mercator': 'mercator', @@ -39,10 +36,10 @@ params.projNames = { }; // name of the axes -params.axesNames = ['lonaxis', 'lataxis']; +exports.axesNames = ['lonaxis', 'lataxis']; // max longitudinal angular span (EXPERIMENTAL) -params.lonaxisSpan = { +exports.lonaxisSpan = { 'orthographic': 180, 'azimuthal equal area': 360, 'azimuthal equidistant': 360, @@ -54,14 +51,14 @@ params.lonaxisSpan = { }; // max latitudinal angular span (EXPERIMENTAL) -params.lataxisSpan = { +exports.lataxisSpan = { 'conic conformal': 150, 'stereographic': 179.5, '*': 180 }; // defaults for each scope -params.scopeDefaults = { +exports.scopeDefaults = { world: { lonaxisRange: [-180, 180], lataxisRange: [-90, 90], @@ -75,7 +72,7 @@ params.scopeDefaults = { }, europe: { lonaxisRange: [-30, 60], - lataxisRange: [30, 80], + lataxisRange: [30, 85], projType: 'conic conformal', projRotate: [15, 0, 0], projParallels: [0, 60] @@ -108,42 +105,63 @@ params.scopeDefaults = { }; // angular pad to avoid rounding error around clip angles -params.clipPad = 1e-3; +exports.clipPad = 1e-3; // map projection precision -params.precision = 0.1; +exports.precision = 0.1; // default land and water fill colors -params.landColor = '#F0DC82'; -params.waterColor = '#3399FF'; +exports.landColor = '#F0DC82'; +exports.waterColor = '#3399FF'; // locationmode to layer name -params.locationmodeToLayer = { +exports.locationmodeToLayer = { 'ISO-3': 'countries', 'USA-states': 'subunits', 'country names': 'countries' }; // SVG element for a sphere (use to frame maps) -params.sphereSVG = {type: 'Sphere'}; +exports.sphereSVG = {type: 'Sphere'}; // N.B. base layer names must be the same as in the topojson files // base layer with a fill color -params.fillLayers = ['ocean', 'land', 'lakes']; +exports.fillLayers = { + ocean: 1, + land: 1, + lakes: 1 +}; // base layer with a only a line color -params.lineLayers = ['subunits', 'countries', 'coastlines', 'rivers', 'frame']; +exports.lineLayers = { + subunits: 1, + countries: 1, + coastlines: 1, + rivers: 1, + frame: 1 +}; -// all base layers - in order -params.baseLayers = [ +exports.layers = [ + 'bg', 'ocean', 'land', 'lakes', 'subunits', 'countries', 'coastlines', 'rivers', - 'lataxis', 'lonaxis', - 'frame' + 'lataxis', 'lonaxis', 'frame', + 'backplot', + 'frontplot' ]; -params.layerNameToAdjective = { +exports.layersForChoropleth = [ + 'bg', + 'ocean', 'land', + 'subunits', 'countries', 'coastlines', + 'lataxis', 'lonaxis', 'frame', + 'backplot', + 'rivers', 'lakes', + 'frontplot' +]; + +exports.layerNameToAdjective = { ocean: 'ocean', land: 'land', lakes: 'lake', @@ -153,6 +171,3 @@ params.layerNameToAdjective = { rivers: 'river', frame: 'frame' }; - -// base layers drawn over choropleth -params.baseLayersOverChoropleth = ['rivers', 'lakes']; diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 2909a3cc6da..282befb3e92 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -6,466 +6,683 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; /* global PlotlyGeoAssets:false */ var d3 = require('d3'); +var Plotly = require('../../plotly'); +var Lib = require('../../lib'); var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Fx = require('../../components/fx'); var Plots = require('../plots'); var Axes = require('../cartesian/axes'); +var dragElement = require('../../components/dragelement'); +var prepSelect = require('../cartesian/select'); -var addProjectionsToD3 = require('./projections'); -var createGeoScale = require('./set_scale'); var createGeoZoom = require('./zoom'); -var createGeoZoomReset = require('./zoom_reset'); var constants = require('./constants'); var topojsonUtils = require('../../lib/topojson_utils'); var topojsonFeature = require('topojson-client').feature; -// add a few projection types to d3.geo -addProjectionsToD3(d3); - +require('./projections')(d3); -function Geo(options) { - this.id = options.id; - this.graphDiv = options.graphDiv; - this.container = options.container; - this.topojsonURL = options.topojsonURL; +function Geo(opts) { + this.id = opts.id; + this.graphDiv = opts.graphDiv; + this.container = opts.container; + this.topojsonURL = opts.topojsonURL; + this.isStatic = opts.staticPlot; this.topojsonName = null; this.topojson = null; - this.projectionType = null; this.projection = null; + this.viewInitial = null; + this.fitScale = null; + this.bounds = null; + this.midPt = null; - this.clipAngle = null; - this.setScale = null; - this.path = null; + this.hasChoropleth = false; + this.traceHash = {}; - this.zoom = null; - this.zoomReset = null; + this.layers = {}; + this.basePaths = {}; + this.dataPaths = {}; + this.dataPoints = {}; - this.makeFramework(); + this.clipDef = null; + this.clipRect = null; + this.bgRect = null; - this.traceHash = {}; + this.makeFramework(); } -module.exports = Geo; - var proto = Geo.prototype; -proto.plot = function(geoCalcData, fullLayout, promises) { - var _this = this, - geoLayout = fullLayout[_this.id], - graphSize = fullLayout._size; +module.exports = function createGeo(opts) { + return new Geo(opts); +}; - var topojsonNameNew, topojsonPath; +proto.plot = function(geoCalcData, fullLayout, promises) { + var _this = this; + var geoLayout = fullLayout[this.id]; + var topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout); - // N.B. 'geoLayout' is unambiguous, no need for 'user' geo layout here + if(_this.topojson === null || topojsonNameNew !== _this.topojsonName) { + _this.topojsonName = topojsonNameNew; - // TODO don't reset projection on all graph edits - _this.projection = null; + if(PlotlyGeoAssets.topojson[_this.topojsonName] === undefined) { + promises.push(_this.fetchTopojson().then(function(topojson) { + PlotlyGeoAssets.topojson[_this.topojsonName] = topojson; + _this.topojson = topojson; + _this.update(geoCalcData, fullLayout); + })); + } else { + _this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName]; + _this.update(geoCalcData, fullLayout); + } + } else { + _this.update(geoCalcData, fullLayout); + } +}; - _this.setScale = createGeoScale(geoLayout, graphSize); - _this.makeProjection(geoLayout); - _this.makePath(); - _this.adjustLayout(geoLayout, graphSize); +proto.fetchTopojson = function() { + var topojsonPath = topojsonUtils.getTopojsonPath( + this.topojsonURL, + this.topojsonName + ); + return new Promise(function(resolve, reject) { + d3.json(topojsonPath, function(err, topojson) { + if(err) { + if(err.status === 404) { + return reject(new Error([ + 'plotly.js could not find topojson file at', + topojsonPath, '.', + 'Make sure the *topojsonURL* plot config option', + 'is set properly.' + ].join(' '))); + } else { + return reject(new Error([ + 'unexpected error while fetching topojson file at', + topojsonPath + ].join(' '))); + } + } + resolve(topojson); + }); + }); +}; - _this.zoom = createGeoZoom(_this, geoLayout); - _this.zoomReset = createGeoZoomReset(_this, geoLayout); - _this.mockAxis = createMockAxis(fullLayout); +proto.update = function(geoCalcData, fullLayout) { + var geoLayout = fullLayout[this.id]; - _this.framework - .call(_this.zoom) - .on('dblclick.zoom', _this.zoomReset); + var hasInvalidBounds = this.updateProjection(fullLayout, geoLayout); + if(hasInvalidBounds) return; - _this.framework.on('mousemove', function() { - var mouse = d3.mouse(this), - lonlat = _this.projection.invert(mouse); + // important: maps with choropleth traces have a different layer order + this.hasChoropleth = false; + for(var i = 0; i < geoCalcData.length; i++) { + if(geoCalcData[i][0].trace.type === 'choropleth') { + this.hasChoropleth = true; + break; + } + } - if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) return; + if(!this.viewInitial) { + this.saveViewInitial(geoLayout); + } - var evt = d3.event; - evt.xpx = mouse[0]; - evt.ypx = mouse[1]; + this.updateBaseLayers(fullLayout, geoLayout); + this.updateDims(fullLayout, geoLayout); + this.updateFx(fullLayout, geoLayout); - _this.xaxis.c2p = function() { return mouse[0]; }; - _this.xaxis.p2c = function() { return lonlat[0]; }; - _this.yaxis.c2p = function() { return mouse[1]; }; - _this.yaxis.p2c = function() { return lonlat[1]; }; + Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout); - Fx.hover(_this.graphDiv, evt, _this.id); - }); + var scatterLayer = this.layers.frontplot.select('.scatterlayer'); + this.dataPoints.point = scatterLayer.selectAll('.point'); + this.dataPoints.text = scatterLayer.selectAll('text'); + this.dataPaths.line = scatterLayer.selectAll('.js-line'); - _this.framework.on('mouseout', function() { - Fx.loneUnhover(fullLayout._toppaper); - }); + var choroplethLayer = this.layers.backplot.select('.choroplethlayer'); + this.dataPaths.choropleth = choroplethLayer.selectAll('path'); - _this.framework.on('click', function() { - Fx.click(_this.graphDiv, d3.event); - }); + this.render(); +}; - topojsonNameNew = topojsonUtils.getTopojsonName(geoLayout); +proto.updateProjection = function(fullLayout, geoLayout) { + var gs = fullLayout._size; + var domain = geoLayout.domain; + var projLayout = geoLayout.projection; + var rotation = projLayout.rotation || {}; + var center = geoLayout.center || {}; - if(_this.topojson === null || topojsonNameNew !== _this.topojsonName) { - _this.topojsonName = topojsonNameNew; + var projection = this.projection = getProjection(geoLayout); - if(PlotlyGeoAssets.topojson[_this.topojsonName] !== undefined) { - _this.topojson = PlotlyGeoAssets.topojson[_this.topojsonName]; - _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); - } - else { - topojsonPath = topojsonUtils.getTopojsonPath( - _this.topojsonURL, - _this.topojsonName - ); - - promises.push(new Promise(function(resolve, reject) { - d3.json(topojsonPath, function(error, topojson) { - if(error) { - if(error.status === 404) { - reject(new Error([ - 'plotly.js could not find topojson file at', - topojsonPath, '.', - 'Make sure the *topojsonURL* plot config option', - 'is set properly.' - ].join(' '))); - } - else { - reject(new Error([ - 'unexpected error while fetching topojson file at', - topojsonPath - ].join(' '))); - } - return; - } - - _this.topojson = topojson; - PlotlyGeoAssets.topojson[_this.topojsonName] = topojson; - - _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); - resolve(); - }); - })); + // set 'pre-fit' projection + projection + .center([center.lon - rotation.lon, center.lat - rotation.lat]) + .rotate([-rotation.lon, -rotation.lat, rotation.roll]) + .parallels(projLayout.parallels); + + // setup subplot extent [[x0,y0], [x1,y1]] + var extent = [[ + gs.l + gs.w * domain.x[0], + gs.t + gs.h * (1 - domain.y[1]) + ], [ + gs.l + gs.w * domain.x[1], + gs.t + gs.h * (1 - domain.y[0]) + ]]; + + var lonaxis = geoLayout.lonaxis; + var lataxis = geoLayout.lataxis; + var rangeBox = makeRangeBox(lonaxis.range, lataxis.range); + + // fit projection 'scale' and 'translate' to set lon/lat ranges + projection.fitExtent(extent, rangeBox); + + var b = this.bounds = projection.getBounds(rangeBox); + var s = this.fitScale = projection.scale(); + var t = projection.translate(); + + if( + !isFinite(b[0][0]) || !isFinite(b[0][1]) || + !isFinite(b[1][0]) || !isFinite(b[1][1]) || + isNaN(t[0]) || isNaN(t[0]) + ) { + var gd = this.graphDiv; + var attrToUnset = ['projection.rotation', 'center', 'lonaxis.range', 'lataxis.range']; + var msg = 'Invalid geo settings, relayout\'ing to default view.'; + var updateObj = {}; + + // clear all attribute that could cause invalid bounds, + // clear viewInitial to update reset-view behavior + + for(var i = 0; i < attrToUnset.length; i++) { + updateObj[this.id + '.' + attrToUnset[i]] = null; } - } - else _this.onceTopojsonIsLoaded(geoCalcData, geoLayout); - // TODO handle topojson-is-loading case - // to avoid making multiple request while streaming -}; + this.viewInitial = null; -proto.onceTopojsonIsLoaded = function(geoCalcData, geoLayout) { - this.drawLayout(geoLayout); + Lib.warn(msg); + gd._promises.push(Plotly.relayout(gd, updateObj)); + return msg; + } - Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout); + // px coordinates of view mid-point, + // useful to update `geo.center` after interactions + var midPt = this.midPt = [ + (b[0][0] + b[1][0]) / 2, + (b[0][1] + b[1][1]) / 2 + ]; - this.render(); + // adjust projection to user setting + projection + .scale(projLayout.scale * s) + .translate([t[0] + (midPt[0] - t[0]), t[1] + (midPt[1] - t[1])]) + .clipExtent(b); + + // the 'albers usa' projection does not expose a 'center' method + // so here's this hack to make it respond to 'geoLayout.center' + if(geoLayout._isAlbersUsa) { + var centerPx = projection([center.lon, center.lat]); + var tt = projection.translate(); + + projection.translate([ + tt[0] - (centerPx[0] - tt[0]), + tt[1] - (centerPx[1] - tt[1]) + ]); + } }; -proto.makeProjection = function(geoLayout) { - var projLayout = geoLayout.projection, - projType = projLayout.type, - isNew = this.projection === null || projType !== this.projectionType, - projection; +proto.updateBaseLayers = function(fullLayout, geoLayout) { + var _this = this; + var topojson = _this.topojson; + var layers = _this.layers; + var basePaths = _this.basePaths; - if(isNew) { - this.projectionType = projType; - projection = this.projection = d3.geo[constants.projNames[projType]](); + function isAxisLayer(d) { + return (d === 'lonaxis' || d === 'lataxis'); } - else projection = this.projection; - projection - .translate(projLayout._translate0) - .precision(constants.precision); - - if(!geoLayout._isAlbersUsa) { - projection - .rotate(projLayout._rotate) - .center(projLayout._center); + function isLineLayer(d) { + return Boolean(constants.lineLayers[d]); } - if(geoLayout._clipAngle) { - this.clipAngle = geoLayout._clipAngle; // needed in proto.render - projection - .clipAngle(geoLayout._clipAngle - constants.clipPad); + function isFillLayer(d) { + return Boolean(constants.fillLayers[d]); } - else this.clipAngle = null; // for graph edits - if(projLayout.parallels) { - projection - .parallels(projLayout.parallels); - } - - if(isNew) this.setScale(projection); - - projection - .translate(projLayout._translate) - .scale(projLayout._scale); -}; + var allLayers = this.hasChoropleth ? + constants.layersForChoropleth : + constants.layers; -proto.makePath = function() { - this.path = d3.geo.path().projection(this.projection); -}; + var layerData = allLayers.filter(function(d) { + return (isLineLayer(d) || isFillLayer(d)) ? geoLayout['show' + d] : + isAxisLayer(d) ? geoLayout[d].showgrid : + true; + }); -proto.makeFramework = function() { - var fullLayout = this.graphDiv._fullLayout; - var clipId = 'clip' + fullLayout._uid + this.id; + var join = _this.framework.selectAll('.layer') + .data(layerData, String); - var defGroup = fullLayout._defs.selectAll('g.clips') - .data([0]); - defGroup.enter().append('g') - .classed('clips', true); + join.exit().each(function(d) { + delete layers[d]; + delete basePaths[d]; + d3.select(this).remove(); + }); - var clipDef = this.clipDef = defGroup.selectAll('#' + clipId) - .data([0]); + join.enter().append('g') + .attr('class', function(d) { return 'layer ' + d; }) + .each(function(d) { + var layer = layers[d] = d3.select(this); + + if(d === 'bg') { + _this.bgRect = layer.append('rect') + .style('pointer-events', 'all'); + } else if(isAxisLayer(d)) { + basePaths[d] = layer.append('path') + .style('fill', 'none'); + } else if(d === 'backplot') { + layer.append('g') + .classed('choroplethlayer', true); + } else if(d === 'frontplot') { + layer.append('g') + .classed('scatterlayer', true); + } else if(isLineLayer(d)) { + basePaths[d] = layer.append('path') + .style('fill', 'none') + .style('stroke-miterlimit', 2); + } else if(isFillLayer(d)) { + basePaths[d] = layer.append('path') + .style('stroke', 'none'); + } + }); + + join.order(); + + join.each(function(d) { + var path = basePaths[d]; + var adj = constants.layerNameToAdjective[d]; + + if(d === 'frame') { + path.datum(constants.sphereSVG); + } else if(isLineLayer(d) || isFillLayer(d)) { + path.datum(topojsonFeature(topojson, topojson.objects[d])); + } else if(isAxisLayer(d)) { + path.datum(makeGraticule(d, geoLayout)) + .call(Color.stroke, geoLayout[d].gridcolor) + .call(Drawing.dashLine, '', geoLayout[d].gridwidth); + } - clipDef.enter().append('clipPath').attr('id', clipId) - .append('rect'); + if(isLineLayer(d)) { + path.call(Color.stroke, geoLayout[adj + 'color']) + .call(Drawing.dashLine, '', geoLayout[adj + 'width']); + } else if(isFillLayer(d)) { + path.call(Color.fill, geoLayout[adj + 'color']); + } + }); +}; - var framework = this.framework = d3.select(this.container).append('g'); +proto.updateDims = function(fullLayout, geoLayout) { + var b = this.bounds; + var hFrameWidth = (geoLayout.framewidth || 0) / 2; - framework - .attr('class', 'geo ' + this.id) - .style('pointer-events', 'all') - .call(Drawing.setClipUrl, clipId); + var l = b[0][0] - hFrameWidth; + var t = b[0][1] - hFrameWidth; + var w = b[1][0] - l + hFrameWidth; + var h = b[1][1] - t + hFrameWidth; - framework.append('g') - .attr('class', 'bglayer') - .append('rect'); + Drawing.setRect(this.clipRect, l, t, w, h); - framework.append('g').attr('class', 'baselayer'); - framework.append('g').attr('class', 'choroplethlayer'); - framework.append('g').attr('class', 'baselayeroverchoropleth'); - framework.append('g').attr('class', 'scattergeolayer'); + this.bgRect + .call(Drawing.setRect, l, t, w, h) + .call(Color.fill, geoLayout.bgcolor); - // N.B. disable dblclick zoom default - framework.on('dblclick.zoom', null); + this.xaxis._offset = l; + this.xaxis._length = w; - this.xaxis = { _id: 'x' }; - this.yaxis = { _id: 'y' }; + this.yaxis._offset = t; + this.yaxis._length = h; }; -proto.adjustLayout = function(geoLayout, graphSize) { - var domain = geoLayout.domain; +proto.updateFx = function(fullLayout, geoLayout) { + var _this = this; + var gd = _this.graphDiv; + var bgRect = _this.bgRect; + var dragMode = fullLayout.dragmode; - var left = graphSize.l + graphSize.w * domain.x[0] + geoLayout._marginX, - top = graphSize.t + graphSize.h * (1 - domain.y[1]) + geoLayout._marginY; + if(_this.isStatic) return; - Drawing.setTranslate(this.framework, left, top); + function zoomReset() { + var viewInitial = _this.viewInitial; + var updateObj = {}; - var dimsAttrs = { - x: 0, - y: 0, - width: geoLayout._width, - height: geoLayout._height - }; + for(var k in viewInitial) { + updateObj[_this.id + '.' + k] = viewInitial[k]; + } - this.clipDef.select('rect') - .attr(dimsAttrs); + Plotly.relayout(gd, updateObj); + gd.emit('plotly_doubleclick', null); + } - this.framework.select('.bglayer').select('rect') - .attr(dimsAttrs) - .call(Color.fill, geoLayout.bgcolor); + function invert(lonlat) { + return _this.projection.invert([ + lonlat[0] + _this.xaxis._offset, + lonlat[1] + _this.yaxis._offset + ]); + } - this.xaxis._offset = left; - this.xaxis._length = geoLayout._width; + if(dragMode === 'pan') { + bgRect.node().onmousedown = null; + bgRect.call(createGeoZoom(_this, geoLayout)); + bgRect.on('dblclick.zoom', zoomReset); + } + else if(dragMode === 'select' || dragMode === 'lasso') { + bgRect.on('.zoom', null); + + var fillRangeItems; + + if(dragMode === 'select') { + fillRangeItems = function(eventData, poly) { + var ranges = eventData.range = {}; + ranges[_this.id] = [ + invert([poly.xmin, poly.ymin]), + invert([poly.xmax, poly.ymax]) + ]; + }; + } else if(dragMode === 'lasso') { + fillRangeItems = function(eventData, poly, pts) { + var dataPts = eventData.lassoPoints = {}; + dataPts[_this.id] = pts.filtered.map(invert); + }; + } - this.yaxis._offset = top; - this.yaxis._length = geoLayout._height; -}; + var dragOptions = { + element: _this.bgRect.node(), + gd: gd, + plotinfo: { + xaxis: _this.xaxis, + yaxis: _this.yaxis, + fillRangeItems: fillRangeItems + }, + xaxes: [_this.xaxis], + yaxes: [_this.yaxis], + subplot: _this.id + }; + + dragOptions.prepFn = function(e, startX, startY) { + prepSelect(e, startX, startY, dragOptions, dragMode); + }; + + dragOptions.doneFn = function(dragged, numClicks) { + if(numClicks === 2) { + fullLayout._zoomlayer.selectAll('.select-outline').remove(); + } + }; + + dragElement.init(dragOptions); + } -proto.drawTopo = function(selection, layerName, geoLayout) { - if(geoLayout['show' + layerName] !== true) return; + bgRect.on('mousemove', function() { + var lonlat = _this.projection.invert(d3.mouse(this)); - var topojson = this.topojson, - datum = layerName === 'frame' ? - constants.sphereSVG : - topojsonFeature(topojson, topojson.objects[layerName]); + if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) { + return dragElement.unhover(gd, d3.event); + } - selection.append('g') - .datum(datum) - .attr('class', layerName) - .append('path') - .attr('class', 'basepath'); -}; + _this.xaxis.p2c = function() { return lonlat[0]; }; + _this.yaxis.p2c = function() { return lonlat[1]; }; -function makeGraticule(lonaxisRange, lataxisRange, step) { - return d3.geo.graticule() - .extent([ - [lonaxisRange[0], lataxisRange[0]], - [lonaxisRange[1], lataxisRange[1]] - ]) - .step(step); -} + Fx.hover(gd, d3.event, _this.id); + }); -proto.drawGraticule = function(selection, axisName, geoLayout) { - var axisLayout = geoLayout[axisName]; + bgRect.on('mouseout', function() { + dragElement.unhover(gd, d3.event); + }); - if(axisLayout.showgrid !== true) return; - - var scopeDefaults = constants.scopeDefaults[geoLayout.scope], - lonaxisRange = scopeDefaults.lonaxisRange, - lataxisRange = scopeDefaults.lataxisRange, - step = axisName === 'lonaxis' ? - [axisLayout.dtick] : - [0, axisLayout.dtick], - graticule = makeGraticule(lonaxisRange, lataxisRange, step); - - selection.append('g') - .datum(graticule) - .attr('class', axisName + 'graticule') - .append('path') - .attr('class', 'graticulepath'); + bgRect.on('click', function() { + Fx.click(gd, d3.event); + }); }; -proto.drawLayout = function(geoLayout) { - var gBaseLayer = this.framework.select('g.baselayer'), - baseLayers = constants.baseLayers, - axesNames = constants.axesNames, - layerName; +proto.makeFramework = function() { + var _this = this; + var fullLayout = _this.graphDiv._fullLayout; + var clipId = 'clip' + fullLayout._uid + _this.id; - // TODO move to more d3-idiomatic pattern (that's work on replot) - // N.B. html('') does not work in IE11 - gBaseLayer.selectAll('*').remove(); + _this.clipDef = fullLayout._clips.append('clipPath') + .attr('id', clipId); - for(var i = 0; i < baseLayers.length; i++) { - layerName = baseLayers[i]; + _this.clipRect = _this.clipDef.append('rect'); - if(axesNames.indexOf(layerName) !== -1) { - this.drawGraticule(gBaseLayer, layerName, geoLayout); - } - else this.drawTopo(gBaseLayer, layerName, geoLayout); - } + _this.framework = d3.select(_this.container).append('g') + .attr('class', 'geo ' + _this.id) + .call(Drawing.setClipUrl, clipId); - this.styleLayout(geoLayout); -}; + // sane lonlat to px + _this.project = function(v) { + var px = _this.projection(v); + return px ? + [px[0] - _this.xaxis._offset, px[1] - _this.yaxis._offset] : + [null, null]; + }; -function styleFillLayer(selection, layerName, geoLayout) { - var layerAdj = constants.layerNameToAdjective[layerName]; + _this.xaxis = { + _id: 'x', + c2p: function(v) { return _this.project(v)[0]; } + }; - selection.select('.' + layerName) - .selectAll('path') - .attr('stroke', 'none') - .call(Color.fill, geoLayout[layerAdj + 'color']); -} + _this.yaxis = { + _id: 'y', + c2p: function(v) { return _this.project(v)[1]; } + }; -function styleLineLayer(selection, layerName, geoLayout) { - var layerAdj = constants.layerNameToAdjective[layerName]; + // mock axis for hover formatting + _this.mockAxis = { + type: 'linear', + showexponent: 'all', + exponentformat: 'B' + }; + Axes.setConvert(_this.mockAxis, fullLayout); +}; - selection.select('.' + layerName) - .selectAll('path') - .attr('fill', 'none') - .call(Color.stroke, geoLayout[layerAdj + 'color']) - .call(Drawing.dashLine, '', geoLayout[layerAdj + 'width']); -} +proto.saveViewInitial = function(geoLayout) { + var center = geoLayout.center || {}; + var projLayout = geoLayout.projection; + var rotation = projLayout.rotation || {}; + + if(geoLayout._isScoped) { + this.viewInitial = { + 'center.lon': center.lon, + 'center.lat': center.lat, + 'projection.scale': projLayout.scale + }; + } else if(geoLayout._isClipped) { + this.viewInitial = { + 'projection.scale': projLayout.scale, + 'projection.rotation.lon': rotation.lon, + 'projection.rotation.lat': rotation.lat + }; + } else { + this.viewInitial = { + 'center.lon': center.lon, + 'center.lat': center.lat, + 'projection.scale': projLayout.scale, + 'projection.rotation.lon': rotation.lon + }; + } +}; -function styleGraticule(selection, axisName, geoLayout) { - selection.select('.' + axisName + 'graticule') - .selectAll('path') - .attr('fill', 'none') - .call(Color.stroke, geoLayout[axisName].gridcolor) - .call(Drawing.dashLine, '', geoLayout[axisName].gridwidth); -} +// [hot code path] (re)draw all paths which depend on the projection +proto.render = function() { + var projection = this.projection; + var pathFn = projection.getPath(); + var k; -proto.styleLayer = function(selection, layerName, geoLayout) { - var fillLayers = constants.fillLayers, - lineLayers = constants.lineLayers; + function translatePoints(d) { + var lonlatPx = projection(d.lonlat); + return lonlatPx ? + 'translate(' + lonlatPx[0] + ',' + lonlatPx[1] + ')' : + null; + } - if(fillLayers.indexOf(layerName) !== -1) { - styleFillLayer(selection, layerName, geoLayout); + function hideShowPoints(d) { + return projection.isLonLatOverEdges(d.lonlat) ? 'none' : null; } - else if(lineLayers.indexOf(layerName) !== -1) { - styleLineLayer(selection, layerName, geoLayout); + + for(k in this.basePaths) { + this.basePaths[k].attr('d', pathFn); } -}; -proto.styleLayout = function(geoLayout) { - var gBaseLayer = this.framework.select('g.baselayer'), - baseLayers = constants.baseLayers, - axesNames = constants.axesNames, - layerName; + for(k in this.dataPaths) { + this.dataPaths[k].attr('d', function(d) { return pathFn(d.geojson); }); + } - for(var i = 0; i < baseLayers.length; i++) { - layerName = baseLayers[i]; + for(k in this.dataPoints) { + this.dataPoints[k] + .attr('display', hideShowPoints) + .attr('transform', translatePoints); + } +}; - if(axesNames.indexOf(layerName) !== -1) { - styleGraticule(gBaseLayer, layerName, geoLayout); +// Helper that wraps d3.geo[/* projection name /*]() which: +// +// - adds 'fitExtent' (available in d3 v4) +// - adds 'getPath', 'getBounds' convenience methods +// - scopes logic related to 'clipAngle' +// - adds 'isLonLatOverEdges' method +// - sets projection precision +// - sets methods that aren't always defined depending +// on the projection type to a dummy 'd3-esque' function, +// +// This wrapper alleviates subsequent code of (many) annoying if-statements. +function getProjection(geoLayout) { + var projLayout = geoLayout.projection; + var projType = projLayout.type; + + var projection = d3.geo[constants.projNames[projType]](); + + var clipAngle = geoLayout._isClipped ? + constants.lonaxisSpan[projType] / 2 : + null; + + var methods = ['center', 'rotate', 'parallels', 'clipExtent']; + var dummyFn = function(_) { return _ ? projection : []; }; + + for(var i = 0; i < methods.length; i++) { + var m = methods[i]; + if(typeof projection[m] !== 'function') { + projection[m] = dummyFn; } - else this.styleLayer(gBaseLayer, layerName, geoLayout); } -}; -proto.isLonLatOverEdges = function(lonlat) { - var clipAngle = this.clipAngle; + projection.isLonLatOverEdges = function(lonlat) { + if(projection(lonlat) === null) { + return true; + } - if(clipAngle === null) return false; + if(clipAngle) { + var r = projection.rotate(); + var angle = d3.geo.distance(lonlat, [-r[0], -r[1]]); + var maxAngle = clipAngle * Math.PI / 180; + return angle > maxAngle; + } else { + return false; + } + }; - var p = this.projection.rotate(), - angle = d3.geo.distance(lonlat, [-p[0], -p[1]]), - maxAngle = clipAngle * Math.PI / 180; + projection.getPath = function() { + return d3.geo.path().projection(projection); + }; - return angle > maxAngle; -}; + projection.getBounds = function(object) { + return projection.getPath().bounds(object); + }; -// [hot code path] (re)draw all paths which depend on the projection -proto.render = function() { - var _this = this, - framework = _this.framework, - gChoropleth = framework.select('g.choroplethlayer'), - gScatterGeo = framework.select('g.scattergeolayer'), - path = _this.path; + // adapted from d3 v4: + // https://github.com/d3/d3-geo/blob/master/src/projection/fit.js + projection.fitExtent = function(extent, object) { + var w = extent[1][0] - extent[0][0]; + var h = extent[1][1] - extent[0][1]; + var clip = projection.clipExtent && projection.clipExtent(); - function translatePoints(d) { - var lonlatPx = _this.projection(d.lonlat); - if(!lonlatPx) return null; + projection + .scale(150) + .translate([0, 0]); - return 'translate(' + lonlatPx[0] + ',' + lonlatPx[1] + ')'; - } + if(clip) projection.clipExtent(null); - // hide paths over edges of clipped projections - function hideShowPoints(d) { - return _this.isLonLatOverEdges(d.lonlat) ? '0' : '1.0'; - } + var b = projection.getBounds(object); + var k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1])); + var x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2; + var y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2; - framework.selectAll('path.basepath').attr('d', path); - framework.selectAll('path.graticulepath').attr('d', path); + if(clip) projection.clipExtent(clip); - gChoropleth.selectAll('path.choroplethlocation').attr('d', path); - gChoropleth.selectAll('path.basepath').attr('d', path); + return projection + .scale(k * 150) + .translate([x, y]); + }; - gScatterGeo.selectAll('path.js-line').attr('d', path); + projection.precision(constants.precision); - if(_this.clipAngle !== null) { - gScatterGeo.selectAll('path.point') - .style('opacity', hideShowPoints) - .attr('transform', translatePoints); - gScatterGeo.selectAll('text') - .style('opacity', hideShowPoints) - .attr('transform', translatePoints); + if(clipAngle) { + projection.clipAngle(clipAngle - constants.clipPad); } - else { - gScatterGeo.selectAll('path.point') - .attr('transform', translatePoints); - gScatterGeo.selectAll('text') - .attr('transform', translatePoints); - } -}; -// create a mock axis used to format hover text -function createMockAxis(fullLayout) { - var mockAxis = { - type: 'linear', - showexponent: 'all', - exponentformat: Axes.layoutAttributes.exponentformat.dflt - }; + return projection; +} + +function makeGraticule(axisName, geoLayout) { + var axisLayout = geoLayout[axisName]; + var dtick = axisLayout.dtick; + var scopeDefaults = constants.scopeDefaults[geoLayout.scope]; + var lonaxisRange = scopeDefaults.lonaxisRange; + var lataxisRange = scopeDefaults.lataxisRange; + var step = axisName === 'lonaxis' ? [dtick] : [0, dtick]; - Axes.setConvert(mockAxis, fullLayout); - return mockAxis; + return d3.geo.graticule() + .extent([ + [lonaxisRange[0], lataxisRange[0]], + [lonaxisRange[1], lataxisRange[1]] + ]) + .step(step); +} + +// Returns polygon GeoJSON corresponding to lon/lat range box +// with well-defined direction +// +// Note that clipPad padding is added around range to avoid aliasing. +function makeRangeBox(lon, lat) { + var clipPad = constants.clipPad; + var lon0 = lon[0] + clipPad; + var lon1 = lon[1] - clipPad; + var lat0 = lat[0] + clipPad; + var lat1 = lat[1] - clipPad; + + // to cross antimeridian w/o ambiguity + if(lon0 > 0 && lon1 < 0) lon1 += 360; + + var dlon4 = (lon1 - lon0) / 4; + + return { + type: 'Polygon', + coordinates: [[ + [lon0, lat0], + [lon0, lat1], + [lon0 + dlon4, lat1], + [lon0 + 2 * dlon4, lat1], + [lon0 + 3 * dlon4, lat1], + [lon1, lat1], + [lon1, lat0], + [lon1 - dlon4, lat0], + [lon1 - 2 * dlon4, lat0], + [lon1 - 3 * dlon4, lat0], + [lon0, lat0] + ]] + }; } diff --git a/src/plots/geo/index.js b/src/plots/geo/index.js index c56651f8a6e..076411c0d82 100644 --- a/src/plots/geo/index.js +++ b/src/plots/geo/index.js @@ -9,14 +9,12 @@ 'use strict'; -var Geo = require('./geo'); - +var createGeo = require('./geo'); var Plots = require('../../plots/plots'); var counterRegex = require('../../lib').counterRegex; var GEO = 'geo'; - exports.name = GEO; exports.attr = GEO; @@ -32,29 +30,31 @@ exports.layoutAttributes = require('./layout/layout_attributes'); exports.supplyLayoutDefaults = require('./layout/defaults'); exports.plot = function plotGeo(gd) { - var fullLayout = gd._fullLayout, - calcData = gd.calcdata, - geoIds = Plots.getSubplotIds(fullLayout, GEO); + var fullLayout = gd._fullLayout; + var calcData = gd.calcdata; + var geoIds = Plots.getSubplotIds(fullLayout, GEO); /** * If 'plotly-geo-assets.js' is not included, * initialize object to keep reference to every loaded topojson */ if(window.PlotlyGeoAssets === undefined) { - window.PlotlyGeoAssets = { topojson: {} }; + window.PlotlyGeoAssets = {topojson: {}}; } for(var i = 0; i < geoIds.length; i++) { - var geoId = geoIds[i], - geoCalcData = Plots.getSubplotCalcData(calcData, GEO, geoId), - geo = fullLayout[geoId]._subplot; + var geoId = geoIds[i]; + var geoCalcData = Plots.getSubplotCalcData(calcData, GEO, geoId); + var geoLayout = fullLayout[geoId]; + var geo = geoLayout._subplot; if(!geo) { - geo = new Geo({ + geo = createGeo({ id: geoId, graphDiv: gd, container: fullLayout._geolayer.node(), - topojsonURL: gd._context.topojsonURL + topojsonURL: gd._context.topojsonURL, + staticPlot: gd._context.staticPlot }); fullLayout[geoId]._subplot = geo; @@ -77,3 +77,13 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) } } }; + +exports.updateFx = function(fullLayout) { + var subplotIds = Plots.getSubplotIds(fullLayout, GEO); + + for(var i = 0; i < subplotIds.length; i++) { + var subplotLayout = fullLayout[subplotIds[i]]; + var subplotObj = subplotLayout._subplot; + subplotObj.updateFx(fullLayout, subplotLayout); + } +}; diff --git a/src/plots/geo/layout/axis_attributes.js b/src/plots/geo/layout/axis_attributes.js deleted file mode 100644 index a03ea496f05..00000000000 --- a/src/plots/geo/layout/axis_attributes.js +++ /dev/null @@ -1,61 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - -'use strict'; - -var colorAttrs = require('../../../components/color/attributes'); - - -module.exports = { - range: { - valType: 'info_array', - role: 'info', - items: [ - {valType: 'number'}, - {valType: 'number'} - ], - description: 'Sets the range of this axis (in degrees).' - }, - showgrid: { - valType: 'boolean', - role: 'info', - dflt: false, - description: 'Sets whether or not graticule are shown on the map.' - }, - tick0: { - valType: 'number', - role: 'info', - description: [ - 'Sets the graticule\'s starting tick longitude/latitude.' - ].join(' ') - }, - dtick: { - valType: 'number', - role: 'info', - description: [ - 'Sets the graticule\'s longitude/latitude tick step.' - ].join(' ') - }, - gridcolor: { - valType: 'color', - role: 'style', - dflt: colorAttrs.lightLine, - description: [ - 'Sets the graticule\'s stroke color.' - ].join(' ') - }, - gridwidth: { - valType: 'number', - role: 'style', - min: 0, - dflt: 1, - description: [ - 'Sets the graticule\'s stroke width (in px).' - ].join(' ') - } -}; diff --git a/src/plots/geo/layout/axis_defaults.js b/src/plots/geo/layout/axis_defaults.js deleted file mode 100644 index f3ccf86f885..00000000000 --- a/src/plots/geo/layout/axis_defaults.js +++ /dev/null @@ -1,72 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - - -'use strict'; - -var Lib = require('../../../lib'); -var constants = require('../constants'); -var axisAttributes = require('./axis_attributes'); - - -module.exports = function supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut) { - var axesNames = constants.axesNames; - - var axisIn, axisOut; - - function coerce(attr, dflt) { - return Lib.coerce(axisIn, axisOut, axisAttributes, attr, dflt); - } - - function getRangeDflt(axisName) { - var scope = geoLayoutOut.scope; - - var projLayout, projType, projRotation, rotateAngle, dfltSpans, halfSpan; - - if(scope === 'world') { - projLayout = geoLayoutOut.projection; - projType = projLayout.type; - projRotation = projLayout.rotation; - dfltSpans = constants[axisName + 'Span']; - - halfSpan = dfltSpans[projType] !== undefined ? - dfltSpans[projType] / 2 : - dfltSpans['*'] / 2; - rotateAngle = axisName === 'lonaxis' ? - projRotation.lon : - projRotation.lat; - - return [rotateAngle - halfSpan, rotateAngle + halfSpan]; - } - else return constants.scopeDefaults[scope][axisName + 'Range']; - } - - for(var i = 0; i < axesNames.length; i++) { - var axisName = axesNames[i]; - axisIn = geoLayoutIn[axisName] || {}; - axisOut = {}; - - var rangeDflt = getRangeDflt(axisName); - - var range = coerce('range', rangeDflt); - - Lib.noneOrAll(axisIn.range, axisOut.range, [0, 1]); - - coerce('tick0', range[0]); - coerce('dtick', axisName === 'lonaxis' ? 30 : 10); - - var show = coerce('showgrid'); - if(show) { - coerce('gridcolor'); - coerce('gridwidth'); - } - - geoLayoutOut[axisName] = axisOut; - geoLayoutOut[axisName]._fullRange = rangeDflt; - } -}; diff --git a/src/plots/geo/layout/defaults.js b/src/plots/geo/layout/defaults.js index 8fa7af85365..dd4eb737e02 100644 --- a/src/plots/geo/layout/defaults.js +++ b/src/plots/geo/layout/defaults.js @@ -12,8 +12,8 @@ var handleSubplotDefaults = require('../../subplot_defaults'); var constants = require('../constants'); var layoutAttributes = require('./layout_attributes'); -var supplyGeoAxisLayoutDefaults = require('./axis_defaults'); +var axesNames = constants.axesNames; module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { handleSubplotDefaults(layoutIn, layoutOut, fullData, { @@ -27,24 +27,64 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) { var show; + var resolution = coerce('resolution'); var scope = coerce('scope'); - var isScoped = (scope !== 'world'); var scopeParams = constants.scopeDefaults[scope]; - var resolution = coerce('resolution'); - var projType = coerce('projection.type', scopeParams.projType); - var isAlbersUsa = projType === 'albers usa'; - var isConic = projType.indexOf('conic') !== -1; + var isAlbersUsa = geoLayoutOut._isAlbersUsa = projType === 'albers usa'; + + // no other scopes are allowed for 'albers usa' projection + if(isAlbersUsa) scope = geoLayoutOut.scope = 'usa'; + + var isScoped = geoLayoutOut._isScoped = (scope !== 'world'); + var isConic = geoLayoutOut._isConic = projType.indexOf('conic') !== -1; + geoLayoutOut._isClipped = !!constants.lonaxisSpan[projType]; + + for(var i = 0; i < axesNames.length; i++) { + var axisName = axesNames[i]; + var dtickDflt = [30, 10][i]; + var rangeDflt; + + if(isScoped) { + rangeDflt = scopeParams[axisName + 'Range']; + } else { + var dfltSpans = constants[axisName + 'Span']; + var hSpan = (dfltSpans[projType] || dfltSpans['*']) / 2; + var rot = coerce( + 'projection.rotation.' + axisName.substr(0, 3), + scopeParams.projRotate[i] + ); + rangeDflt = [rot - hSpan, rot + hSpan]; + } - if(isConic) { - var dfltProjParallels = scopeParams.projParallels || [0, 60]; - coerce('projection.parallels', dfltProjParallels); + var range = coerce(axisName + '.range', rangeDflt); + + coerce(axisName + '.tick0', range[0]); + coerce(axisName + '.dtick', dtickDflt); + + show = coerce(axisName + '.showgrid'); + if(show) { + coerce(axisName + '.gridcolor'); + coerce(axisName + '.gridwidth'); + } } + var lonRange = geoLayoutOut.lonaxis.range; + var latRange = geoLayoutOut.lataxis.range; + + // to cross antimeridian w/o ambiguity + var lon0 = lonRange[0]; + var lon1 = lonRange[1]; + if(lon0 > 0 && lon1 < 0) lon1 += 360; + + var centerLon = (lon0 + lon1) / 2; + var projLon; + if(!isAlbersUsa) { - var dfltProjRotate = scopeParams.projRotate || [0, 0, 0]; - coerce('projection.rotation.lon', dfltProjRotate[0]); + var dfltProjRotate = isScoped ? scopeParams.projRotate : [centerLon, 0, 0]; + + projLon = coerce('projection.rotation.lon', dfltProjRotate[0]); coerce('projection.rotation.lat', dfltProjRotate[1]); coerce('projection.rotation.roll', dfltProjRotate[2]); @@ -57,7 +97,28 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) { show = coerce('showocean'); if(show) coerce('oceancolor'); } - else geoLayoutOut.scope = 'usa'; + + var centerLonDflt; + var centerLatDflt; + + if(isAlbersUsa) { + // 'albers usa' does not have a 'center', + // these values were found using via: + // projection.invert([geoLayout.center.lon, geoLayoutIn.center.lat]) + centerLonDflt = -96.6; + centerLatDflt = 38.7; + } else { + centerLonDflt = isScoped ? centerLon : projLon; + centerLatDflt = (latRange[0] + latRange[1]) / 2; + } + + coerce('center.lon', centerLonDflt); + coerce('center.lat', centerLatDflt); + + if(isConic) { + var dfltProjParallels = scopeParams.projParallels || [0, 60]; + coerce('projection.parallels', dfltProjParallels); + } coerce('projection.scale'); @@ -98,20 +159,4 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) { } coerce('bgcolor'); - - supplyGeoAxisLayoutDefaults(geoLayoutIn, geoLayoutOut); - - // bind a few helper variables - geoLayoutOut._isHighRes = resolution === 50; - geoLayoutOut._clipAngle = constants.lonaxisSpan[projType] / 2; - geoLayoutOut._isAlbersUsa = isAlbersUsa; - geoLayoutOut._isConic = isConic; - geoLayoutOut._isScoped = isScoped; - - var rotation = geoLayoutOut.projection.rotation || {}; - geoLayoutOut.projection._rotate = [ - -rotation.lon || 0, - -rotation.lat || 0, - rotation.roll || 0 - ]; } diff --git a/src/plots/geo/layout/layout_attributes.js b/src/plots/geo/layout/layout_attributes.js index 98a5043dcfa..a6da089854e 100644 --- a/src/plots/geo/layout/layout_attributes.js +++ b/src/plots/geo/layout/layout_attributes.js @@ -10,9 +10,59 @@ var colorAttrs = require('../../../components/color/attributes'); var constants = require('../constants'); -var geoAxesAttrs = require('./axis_attributes'); var overrideAll = require('../../../plot_api/edit_types').overrideAll; +var geoAxesAttrs = { + range: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number'}, + {valType: 'number'} + ], + description: [ + 'Sets the range of this axis (in degrees),', + 'sets the map\'s clipped coordinates.' + ].join(' ') + }, + showgrid: { + valType: 'boolean', + role: 'info', + dflt: false, + description: 'Sets whether or not graticule are shown on the map.' + }, + tick0: { + valType: 'number', + role: 'info', + description: [ + 'Sets the graticule\'s starting tick longitude/latitude.' + ].join(' ') + }, + dtick: { + valType: 'number', + role: 'info', + description: [ + 'Sets the graticule\'s longitude/latitude tick step.' + ].join(' ') + }, + gridcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.lightLine, + description: [ + 'Sets the graticule\'s stroke color.' + ].join(' ') + }, + gridwidth: { + valType: 'number', + role: 'style', + min: 0, + dflt: 1, + description: [ + 'Sets the graticule\'s stroke width (in px).' + ].join(' ') + } +}; module.exports = overrideAll({ domain: { @@ -25,8 +75,11 @@ module.exports = overrideAll({ ], dflt: [0, 1], description: [ - 'Sets the horizontal domain of this map', - '(in plot fraction).' + 'Sets the maximum horizontal domain of this map', + '(in plot fraction).', + 'Note that geo subplots are constrained by domain.', + 'In general, when `projection.scale` is set to 1.', + 'a map will fit either its x or y domain, but not both. ' ].join(' ') }, y: { @@ -38,8 +91,11 @@ module.exports = overrideAll({ ], dflt: [0, 1], description: [ - 'Sets the vertical domain of this map', - '(in plot fraction).' + 'Sets the maximum vertical domain of this map', + '(in plot fraction).', + 'Note that geo subplots are constrained by domain.', + 'In general, when `projection.scale` is set to 1.', + 'a map will fit either its x or y domain, but not both. ' ].join(' ') } }, @@ -75,7 +131,8 @@ module.exports = overrideAll({ role: 'info', description: [ 'Rotates the map along parallels', - '(in degrees East).' + '(in degrees East).', + 'Defaults to the center of the `lonaxis.range` values.' ].join(' ') }, lat: { @@ -112,9 +169,32 @@ module.exports = overrideAll({ valType: 'number', role: 'info', min: 0, - max: 10, dflt: 1, - description: 'Zooms in or out on the map view.' + description: [ + 'Zooms in or out on the map view.', + 'A scale of *1* corresponds to the largest zoom level', + 'that fits the map\'s lon and lat ranges. ' + ].join(' ') + }, + }, + center: { + lon: { + valType: 'number', + role: 'info', + description: [ + 'Sets the longitude of the map\'s center.', + 'By default, the map\'s longitude center lies at the middle of the longitude range', + 'for scoped projection and above `projection.rotation.lon` otherwise.' + ].join(' ') + }, + lat: { + valType: 'number', + role: 'info', + description: [ + 'Sets the latitude of the map\'s center.', + 'For all projection types, the map\'s latitude center lies', + 'at the middle of the latitude range by default.' + ].join(' ') } }, showcoastlines: { diff --git a/src/plots/geo/set_scale.js b/src/plots/geo/set_scale.js deleted file mode 100644 index 66ef39f0983..00000000000 --- a/src/plots/geo/set_scale.js +++ /dev/null @@ -1,149 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - - -'use strict'; - -var d3 = require('d3'); - -var clipPad = require('./constants').clipPad; - -function createGeoScale(geoLayout, graphSize) { - var projLayout = geoLayout.projection, - lonaxisLayout = geoLayout.lonaxis, - lataxisLayout = geoLayout.lataxis, - geoDomain = geoLayout.domain, - frameWidth = geoLayout.framewidth || 0; - - // width & height the geo div - var geoWidth = graphSize.w * (geoDomain.x[1] - geoDomain.x[0]), - geoHeight = graphSize.h * (geoDomain.y[1] - geoDomain.y[0]); - - // add padding around range to avoid aliasing - var lon0 = lonaxisLayout.range[0] + clipPad, - lon1 = lonaxisLayout.range[1] - clipPad, - lat0 = lataxisLayout.range[0] + clipPad, - lat1 = lataxisLayout.range[1] - clipPad, - lonfull0 = lonaxisLayout._fullRange[0] + clipPad, - lonfull1 = lonaxisLayout._fullRange[1] - clipPad, - latfull0 = lataxisLayout._fullRange[0] + clipPad, - latfull1 = lataxisLayout._fullRange[1] - clipPad; - - // initial translation (makes the math easier) - projLayout._translate0 = [ - graphSize.l + geoWidth / 2, graphSize.t + geoHeight / 2 - ]; - - - // center of the projection is given by - // the lon/lat ranges and the rotate angle - var dlon = lon1 - lon0, - dlat = lat1 - lat0, - c0 = [lon0 + dlon / 2, lat0 + dlat / 2], - r = projLayout._rotate; - - projLayout._center = [c0[0] + r[0], c0[1] + r[1]]; - - // needs a initial projection; it is called from makeProjection - var setScale = function(projection) { - var scale0 = projection.scale(), - translate0 = projLayout._translate0, - rangeBox = makeRangeBox(lon0, lat0, lon1, lat1), - fullRangeBox = makeRangeBox(lonfull0, latfull0, lonfull1, latfull1); - - var scale, translate, bounds, fullBounds; - - // Inspired by: http://stackoverflow.com/a/14654988/4068492 - // using the path determine the bounds of the current map and use - // these to determine better values for the scale and translation - - function getScale(bounds) { - return Math.min( - scale0 * geoWidth / (bounds[1][0] - bounds[0][0]), - scale0 * geoHeight / (bounds[1][1] - bounds[0][1]) - ); - } - - // scale projection given how range box get deformed - // by the projection - bounds = getBounds(projection, rangeBox); - scale = getScale(bounds); - - // similarly, get scale at full range - fullBounds = getBounds(projection, fullRangeBox); - projLayout._fullScale = getScale(fullBounds); - - projection.scale(scale); - - // translate the projection so that the top-left corner - // of the range box is at the top-left corner of the viewbox - bounds = getBounds(projection, rangeBox); - translate = [ - translate0[0] - bounds[0][0] + frameWidth, - translate0[1] - bounds[0][1] + frameWidth - ]; - projLayout._translate = translate; - projection.translate(translate); - - // clip regions out of the range box - // (these are clipping along horizontal/vertical lines) - bounds = getBounds(projection, rangeBox); - if(!geoLayout._isAlbersUsa) projection.clipExtent(bounds); - - // adjust scale one more time with the 'scale' attribute - scale = projLayout.scale * scale; - - // set projection scale and save it - projLayout._scale = scale; - - // save the effective width & height of the geo framework - geoLayout._width = Math.round(bounds[1][0]) + frameWidth; - geoLayout._height = Math.round(bounds[1][1]) + frameWidth; - - // save the margin length induced by the map scaling - geoLayout._marginX = (geoWidth - Math.round(bounds[1][0])) / 2; - geoLayout._marginY = (geoHeight - Math.round(bounds[1][1])) / 2; - }; - - return setScale; -} - -module.exports = createGeoScale; - -// polygon GeoJSON corresponding to lon/lat range box -// with well-defined direction -function makeRangeBox(lon0, lat0, lon1, lat1) { - var dlon4 = (lon1 - lon0) / 4; - - // TODO is this enough to handle ALL cases? - // -- this makes scaling less precise than using d3.geo.graticule - // as great circles can overshoot the boundary - // (that's not a big deal I think) - return { - type: 'Polygon', - coordinates: [ - [ [lon0, lat0], - [lon0, lat1], - [lon0 + dlon4, lat1], - [lon0 + 2 * dlon4, lat1], - [lon0 + 3 * dlon4, lat1], - [lon1, lat1], - [lon1, lat0], - [lon1 - dlon4, lat0], - [lon1 - 2 * dlon4, lat0], - [lon1 - 3 * dlon4, lat0], - [lon0, lat0] ] - ] - }; -} - -// bounds array [[top, left], [bottom, right]] -// of the lon/lat range box -function getBounds(projection, rangeBox) { - return d3.geo.path().projection(projection).bounds(rangeBox); -} diff --git a/src/plots/geo/zoom.js b/src/plots/geo/zoom.js index dd26c33bae8..6330445c543 100644 --- a/src/plots/geo/zoom.js +++ b/src/plots/geo/zoom.js @@ -10,41 +10,66 @@ 'use strict'; var d3 = require('d3'); +var Lib = require('../../lib'); -var radians = Math.PI / 180, - degrees = 180 / Math.PI, - zoomstartStyle = { cursor: 'pointer' }, - zoomendStyle = { cursor: 'auto' }; - +var radians = Math.PI / 180; +var degrees = 180 / Math.PI; +var zoomstartStyle = {cursor: 'pointer'}; +var zoomendStyle = {cursor: 'auto'}; function createGeoZoom(geo, geoLayout) { + var projection = geo.projection; var zoomConstructor; - if(geoLayout._isScoped) zoomConstructor = zoomScoped; - else if(geoLayout._clipAngle) zoomConstructor = zoomClipped; - else zoomConstructor = zoomNonClipped; + if(geoLayout._isScoped) { + zoomConstructor = zoomScoped; + } else if(geoLayout._isClipped) { + zoomConstructor = zoomClipped; + } else { + zoomConstructor = zoomNonClipped; + } // TODO add a conic-specific zoom - return zoomConstructor(geo, geoLayout.projection); + return zoomConstructor(geo, projection); } module.exports = createGeoZoom; // common to all zoom types -function initZoom(projection, projLayout) { - var fullScale = projLayout._fullScale; - +function initZoom(geo, projection) { return d3.behavior.zoom() .translate(projection.translate()) - .scale(projection.scale()) - .scaleExtent([0.5 * fullScale, 100 * fullScale]); + .scale(projection.scale()); +} + +// sync zoom updates with user & full layout +function sync(geo, projection, cb) { + var id = geo.id; + var gd = geo.graphDiv; + var userOpts = gd.layout[id]; + var fullOpts = gd._fullLayout[id]; + + var eventData = {}; + + function set(propStr, val) { + var fullNp = Lib.nestedProperty(fullOpts, propStr); + + if(fullNp.get() !== val) { + fullNp.set(val); + Lib.nestedProperty(userOpts, propStr).set(val); + eventData[id + '.' + propStr] = val; + } + } + + cb(set); + set('projection.scale', projection.scale() / geo.fitScale); + gd.emit('plotly_relayout', eventData); } // zoom for scoped projections -function zoomScoped(geo, projLayout) { - var projection = geo.projection, - zoom = initZoom(projection, projLayout); +function zoomScoped(geo, projection) { + var zoom = initZoom(geo, projection); function handleZoomstart() { d3.select(this).style(zoomstartStyle); @@ -54,12 +79,19 @@ function zoomScoped(geo, projLayout) { projection .scale(d3.event.scale) .translate(d3.event.translate); - geo.render(); } + function syncCb(set) { + var center = projection.invert(geo.midPt); + + set('center.lon', center[0]); + set('center.lat', center[1]); + } + function handleZoomend() { d3.select(this).style(zoomendStyle); + sync(geo, projection, syncCb); } zoom @@ -71,9 +103,8 @@ function zoomScoped(geo, projLayout) { } // zoom for non-clipped projections -function zoomNonClipped(geo, projLayout) { - var projection = geo.projection, - zoom = initZoom(projection, projLayout); +function zoomNonClipped(geo, projection) { + var zoom = initZoom(geo, projection); var INSIDETOLORANCEPXS = 2; @@ -108,7 +139,6 @@ function zoomNonClipped(geo, projLayout) { } projection.scale(d3.event.scale); - projection.translate([translate0[0], d3.event.translate[1]]); if(!zoomPoint) { @@ -127,10 +157,16 @@ function zoomNonClipped(geo, projLayout) { function handleZoomend() { d3.select(this).style(zoomendStyle); + sync(geo, projection, syncCb); + } + + function syncCb(set) { + var rotate = projection.rotate(); + var center = projection.invert(geo.midPt); - // or something like - // http://www.jasondavies.com/maps/gilbert/ - // ... a little harder with multiple base layers + set('projection.rotation.lon', -rotate[0]); + set('center.lon', center[0]); + set('center.lat', center[1]); } zoom @@ -143,10 +179,9 @@ function zoomNonClipped(geo, projLayout) { // zoom for clipped projections // inspired by https://www.jasondavies.com/maps/d3.geo.zoom.js -function zoomClipped(geo, projLayout) { - var projection = geo.projection, - view = {r: projection.rotate(), k: projection.scale()}, - zoom = initZoom(projection, projLayout), +function zoomClipped(geo, projection) { + var view = {r: projection.rotate(), k: projection.scale()}, + zoom = initZoom(geo, projection), event = d3_eventDispatch(zoom, 'zoomstart', 'zoom', 'zoomend'), zooming = 0, zoomOn = zoom.on; @@ -179,7 +214,6 @@ function zoomClipped(geo, projLayout) { // if not, don't do anything new but scale // if it is, then we can assume between will exist below // so we don't need the 'bank' function, whatever that is. - // TODO: is this right? else if(position(projection, mouse1)) { // go back to original projection temporarily // except for scale... that's kind of independent? @@ -212,6 +246,7 @@ function zoomClipped(geo, projLayout) { d3.select(this).style(zoomendStyle); zoomOn.call(zoom, 'zoom', null); zoomended(event.of(this, arguments)); + sync(geo, projection, syncCb); }) .on('zoom.redraw', function() { geo.render(); @@ -229,6 +264,12 @@ function zoomClipped(geo, projLayout) { if(!--zooming) dispatch({type: 'zoomend'}); } + function syncCb(set) { + var _rotate = projection.rotate(); + set('projection.rotation.lon', -_rotate[0]); + set('projection.rotation.lat', -_rotate[1]); + } + return d3.rebind(zoom, event, 'on'); } diff --git a/src/plots/geo/zoom_reset.js b/src/plots/geo/zoom_reset.js deleted file mode 100644 index dc9a543d09f..00000000000 --- a/src/plots/geo/zoom_reset.js +++ /dev/null @@ -1,27 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - - -'use strict'; - -module.exports = function createGeoZoomReset(geo, geoLayout) { - var projection = geo.projection, - zoom = geo.zoom; - - var zoomReset = function() { - geo.makeProjection(geoLayout); - geo.makePath(); - - zoom.scale(projection.scale()); - zoom.translate(projection.translate()); - - geo.render(); - }; - - return zoomReset; -}; diff --git a/src/plots/gl2d/index.js b/src/plots/gl2d/index.js index 3618c491212..177de42ffab 100644 --- a/src/plots/gl2d/index.js +++ b/src/plots/gl2d/index.js @@ -130,3 +130,12 @@ exports.toSVG = function(gd) { scene.destroy(); } }; + +exports.updateFx = function(fullLayout) { + var subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'); + + for(var i = 0; i < subplotIds.length; i++) { + var subplotObj = fullLayout._plots[subplotIds[i]]._scene2d; + subplotObj.updateFx(fullLayout.dragmode); + } +}; diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index 3c003165a8a..e3e2e9094ca 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -128,3 +128,12 @@ exports.cleanId = function cleanId(id) { return SCENE + sceneNum; }; + +exports.updateFx = function(fullLayout) { + var subplotIds = Plots.getSubplotIds(fullLayout, GL3D); + + for(var i = 0; i < subplotIds.length; i++) { + var subplotObj = fullLayout[subplotIds[i]]._scene; + subplotObj.updateFx(fullLayout.dragmode, fullLayout.hovermode); + } +}; diff --git a/src/plots/mapbox/index.js b/src/plots/mapbox/index.js index a63393fe6dd..b417cb59729 100644 --- a/src/plots/mapbox/index.js +++ b/src/plots/mapbox/index.js @@ -155,3 +155,12 @@ function findAccessToken(gd, mapboxIds) { return accessToken; } + +exports.updateFx = function(fullLayout) { + var subplotIds = Plots.getSubplotIds(fullLayout, MAPBOX); + + for(var i = 0; i < subplotIds.length; i++) { + var subplotObj = fullLayout[subplotIds[i]]._subplot; + subplotObj.updateFx(fullLayout); + } +}; diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 719b89168a7..a42f8461f53 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -70,22 +70,17 @@ proto.plot = function(ternaryCalcData, fullLayout) { proto.makeFramework = function(fullLayout) { var _this = this; var ternaryLayout = fullLayout[_this.id]; - - var defGroup = _this.defs.selectAll('g.clips') - .data([0]); - defGroup.enter().append('g') - .classed('clips', true); + var clipId = _this.clipId = 'clip' + _this.layoutId + _this.id; // clippath for this ternary subplot - var clipId = _this.clipId = 'clip' + _this.layoutId + _this.id; - _this.clipDef = defGroup.selectAll('#' + clipId) + _this.clipDef = fullLayout._clips.selectAll('#' + clipId) .data([0]); _this.clipDef.enter().append('clipPath').attr('id', clipId) .append('path').attr('d', 'M0,0Z'); // 'relative' clippath (i.e. no translation) for this ternary subplot var clipIdRelative = _this.clipIdRelative = 'clip-relative' + _this.layoutId + _this.id; - _this.clipDefRelative = defGroup.selectAll('#' + clipIdRelative) + _this.clipDefRelative = fullLayout._clips.selectAll('#' + clipIdRelative) .data([0]); _this.clipDefRelative.enter().append('clipPath').attr('id', clipIdRelative) .append('path').attr('d', 'M0,0Z'); diff --git a/src/traces/carpet/plot.js b/src/traces/carpet/plot.js index 8c469851e90..f83731592ad 100644 --- a/src/traces/carpet/plot.js +++ b/src/traces/carpet/plot.js @@ -38,7 +38,7 @@ function plotOne(gd, plotinfo, cd) { fullLayout = gd._fullLayout; var gridLayer = plotinfo.plot.selectAll('.carpetlayer'); - var clipLayer = makeg(fullLayout._defs, 'g', 'clips'); + var clipLayer = fullLayout._clips; var axisLayer = makeg(gridLayer, 'g', 'carpet' + trace.uid).classed('trace', true); var minorLayer = makeg(axisLayer, 'g', 'minorlayer'); diff --git a/src/traces/choropleth/calc.js b/src/traces/choropleth/calc.js index 5a3eacb14a4..f4e479a98f7 100644 --- a/src/traces/choropleth/calc.js +++ b/src/traces/choropleth/calc.js @@ -9,9 +9,27 @@ 'use strict'; -var colorscaleCalc = require('../../components/colorscale/calc'); +var isNumeric = require('fast-isnumeric'); +var BADNUM = require('../../constants/numerical').BADNUM; +var colorscaleCalc = require('../../components/colorscale/calc'); +var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); module.exports = function calc(gd, trace) { + var len = trace.locations.length; + var calcTrace = new Array(len); + + for(var i = 0; i < len; i++) { + var calcPt = calcTrace[i] = {}; + var loc = trace.locations[i]; + var z = trace.z[i]; + + calcPt.loc = typeof loc === 'string' ? loc : null; + calcPt.z = isNumeric(z) ? z : BADNUM; + } + + arraysToCalcdata(calcTrace, trace); colorscaleCalc(trace, trace.z, '', 'z'); + + return calcTrace; }; diff --git a/src/traces/choropleth/hover.js b/src/traces/choropleth/hover.js index b9b4962102e..71799f22385 100644 --- a/src/traces/choropleth/hover.js +++ b/src/traces/choropleth/hover.js @@ -12,23 +12,39 @@ var Axes = require('../../plots/cartesian/axes'); var attributes = require('./attributes'); -module.exports = function hoverPoints(pointData) { +module.exports = function hoverPoints(pointData, xval, yval) { var cd = pointData.cd; var trace = cd[0].trace; var geo = pointData.subplot; - // set on choropleth paths 'mouseover' - var pt = geo.choroplethHoverPt; - - if(!pt) return; + var pt, i, j, isInside; + + for(i = 0; i < cd.length; i++) { + pt = cd[i]; + isInside = false; + + if(pt._polygons) { + for(j = 0; j < pt._polygons.length; j++) { + if(pt._polygons[j].contains([xval, yval])) { + isInside = !isInside; + } + // for polygons that cross antimeridian as xval is in [-180, 180] + if(pt._polygons[j].contains([xval + 360, yval])) { + isInside = !isInside; + } + } + + if(isInside) break; + } + } - var centroid = geo.projection(pt.properties.ct); + if(!isInside || !pt) return; - pointData.x0 = pointData.x1 = centroid[0]; - pointData.y0 = pointData.y1 = centroid[1]; + pointData.x0 = pointData.x1 = pointData.xa.c2p(pt.ct); + pointData.y0 = pointData.y1 = pointData.ya.c2p(pt.ct); pointData.index = pt.index; - pointData.location = pt.id; + pointData.location = pt.loc; pointData.z = pt.z; makeHoverInfo(pointData, trace, pt, geo.mockAxis); @@ -55,10 +71,11 @@ function makeHoverInfo(pointData, trace, pt, axis) { return Axes.tickText(axis, axis.c2l(val), 'hover').text; } - if(hasIdAsNameLabel) pointData.nameOverride = pt.id; - else { + if(hasIdAsNameLabel) { + pointData.nameOverride = pt.loc; + } else { if(hasName) pointData.nameOverride = trace.name; - if(hasLocation) text.push(pt.id); + if(hasLocation) text.push(pt.loc); } if(hasZ) text.push(formatter(pt.z)); diff --git a/src/traces/choropleth/index.js b/src/traces/choropleth/index.js index f358369cfd3..a5940e41cd6 100644 --- a/src/traces/choropleth/index.js +++ b/src/traces/choropleth/index.js @@ -18,6 +18,7 @@ Choropleth.calc = require('./calc'); Choropleth.plot = require('./plot'); Choropleth.hoverPoints = require('./hover'); Choropleth.eventData = require('./event_data'); +Choropleth.selectPoints = require('./select'); Choropleth.moduleType = 'trace'; Choropleth.name = 'choropleth'; diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js index 9f4ec41e87c..1d2c30d4234 100644 --- a/src/traces/choropleth/plot.js +++ b/src/traces/choropleth/plot.js @@ -11,108 +11,179 @@ var d3 = require('d3'); +var Lib = require('../../lib'); var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Colorscale = require('../../components/colorscale'); +var polygon = require('../../lib/polygon'); var getTopojsonFeatures = require('../../lib/topojson_utils').getTopojsonFeatures; var locationToFeature = require('../../lib/geo_location_utils').locationToFeature; -var arrayToCalcItem = require('../../lib/array_to_calc_item'); -var constants = require('../../plots/geo/constants'); - -module.exports = function plot(geo, calcData, geoLayout) { +module.exports = function plot(geo, calcData) { + for(var i = 0; i < calcData.length; i++) { + calcGeoJSON(calcData[i], geo.topojson); + } function keyFunc(d) { return d[0].trace.uid; } - var framework = geo.framework, - gChoropleth = framework.select('g.choroplethlayer'), - gBaseLayer = framework.select('g.baselayer'), - gBaseLayerOverChoropleth = framework.select('g.baselayeroverchoropleth'), - baseLayersOverChoropleth = constants.baseLayersOverChoropleth, - layerName; - - var gChoroplethTraces = gChoropleth + var gTraces = geo.layers.backplot.select('.choroplethlayer') .selectAll('g.trace.choropleth') .data(calcData, keyFunc); - gChoroplethTraces.enter().append('g') + gTraces.enter().append('g') .attr('class', 'trace choropleth'); - gChoroplethTraces.exit().remove(); + gTraces.exit().remove(); - gChoroplethTraces.each(function(calcTrace) { - var trace = calcTrace[0].trace, - cdi = calcGeoJSON(trace, geo.topojson); + gTraces.each(function(calcTrace) { + var sel = calcTrace[0].node3 = d3.select(this); - var paths = d3.select(this) - .selectAll('path.choroplethlocation') - .data(cdi); + var paths = sel.selectAll('path.choroplethlocation') + .data(Lib.identity); paths.enter().append('path') - .classed('choroplethlocation', true) - .on('mouseover', function(pt) { - geo.choroplethHoverPt = pt; - }) - .on('mouseout', function() { - geo.choroplethHoverPt = null; - }); + .classed('choroplethlocation', true); paths.exit().remove(); }); - // some baselayers are drawn over choropleth - gBaseLayerOverChoropleth.selectAll('*').remove(); - - for(var i = 0; i < baseLayersOverChoropleth.length; i++) { - layerName = baseLayersOverChoropleth[i]; - gBaseLayer.select('g.' + layerName).remove(); - geo.drawTopo(gBaseLayerOverChoropleth, layerName, geoLayout); - geo.styleLayer(gBaseLayerOverChoropleth, layerName, geoLayout); - } - style(geo); }; -function calcGeoJSON(trace, topojson) { - var cdi = [], - locations = trace.locations, - len = locations.length, - features = getTopojsonFeatures(trace, topojson), - markerLine = (trace.marker || {}).line || {}; - - var feature; +function calcGeoJSON(calcTrace, topojson) { + var trace = calcTrace[0].trace; + var len = calcTrace.length; + var features = getTopojsonFeatures(trace, topojson); for(var i = 0; i < len; i++) { - feature = locationToFeature(trace.locationmode, locations[i], features); + var calcPt = calcTrace[i]; + var feature = locationToFeature(trace.locationmode, calcPt.loc, features); - if(!feature) continue; // filter the blank features here + if(!feature) { + calcPt.geojson = null; + continue; + } - // 'data_array' attributes - feature.z = trace.z[i]; - if(trace.text !== undefined) feature.tx = trace.text[i]; - // 'arrayOk' attributes - arrayToCalcItem(markerLine.color, feature, 'mlc', i); - arrayToCalcItem(markerLine.width, feature, 'mlw', i); + calcPt.geojson = feature; + calcPt.ct = feature.properties.ct; + calcPt.index = i; + calcPt._polygons = feature2polygons(feature); + } +} - // for event data - feature.index = i; +function feature2polygons(feature) { + var geometry = feature.geometry; + var coords = geometry.coordinates; + var loc = feature.id; - cdi.push(feature); + var polygons = []; + var appendPolygon, j, k, m; + + function doesCrossAntiMerdian(pts) { + for(var l = 0; l < pts.length - 1; l++) { + if(pts[l][0] > 0 && pts[l + 1][0] < 0) return l; + } + return null; } - if(cdi.length > 0) cdi[0].trace = trace; + if(loc === 'RUS' || loc === 'FJI') { + // Russia and Fiji have landmasses that cross the antimeridian, + // we need to add +360 to their longitude coordinates, so that + // polygon 'contains' doesn't get confused when crossing the antimeridian. + // + // Note that other countries have polygons on either side of the antimeridian + // (e.g. some Aleutian island for the USA), but those don't confuse + // the 'contains' method; these are skipped here. + appendPolygon = function(_pts) { + var pts; + + if(doesCrossAntiMerdian(_pts) === null) { + pts = _pts; + } else { + pts = new Array(_pts.length); + for(m = 0; m < _pts.length; m++) { + // do nut mutate calcdata[i][j].geojson !! + pts[m] = [ + _pts[m][0] < 0 ? _pts[m][0] + 360 : _pts[m][0], + _pts[m][1] + ]; + } + } + + polygons.push(polygon.tester(pts)); + }; + } else if(loc === 'ATA') { + // Antarctica has a landmass that wraps around every longitudes which + // confuses the 'contains' methods. + appendPolygon = function(pts) { + var crossAntiMeridianIndex = doesCrossAntiMerdian(pts); + + // polygon that do not cross anti-meridian need no special handling + if(crossAntiMeridianIndex === null) { + return polygons.push(polygon.tester(pts)); + } + + // stitch polygon by adding pt over South Pole, + // so that it covers the projected region covers all latitudes + // + // Note that the algorithm below only works for polygons that + // start and end on longitude -180 (like the ones built by + // https://github.com/etpinard/sane-topojson). + var stitch = new Array(pts.length + 1); + var si = 0; + + for(m = 0; m < pts.length; m++) { + if(m > crossAntiMeridianIndex) { + stitch[si++] = [pts[m][0] + 360, pts[m][1]]; + } else if(m === crossAntiMeridianIndex) { + stitch[si++] = pts[m]; + stitch[si++] = [pts[m][0], -90]; + } else { + stitch[si++] = pts[m]; + } + } + + // polygon.tester by default appends pt[0] to the points list, + // we must remove it here, to avoid a jump in longitude from 180 to -180, + // that would confuse the 'contains' method + var tester = polygon.tester(stitch); + tester.pts.pop(); + polygons.push(tester); + }; + } else { + // otherwise using same array ref is fine + appendPolygon = function(pts) { + polygons.push(polygon.tester(pts)); + }; + } - return cdi; + switch(geometry.type) { + case 'MultiPolygon': + for(j = 0; j < coords.length; j++) { + for(k = 0; k < coords[j].length; k++) { + appendPolygon(coords[j][k]); + } + } + break; + case 'Polygon': + for(j = 0; j < coords.length; j++) { + appendPolygon(coords[j]); + } + break; + } + + return polygons; } function style(geo) { - geo.framework.selectAll('g.trace.choropleth').each(function(calcTrace) { - var trace = calcTrace[0].trace, - s = d3.select(this), - marker = trace.marker || {}, - markerLine = marker.line || {}; + var gTraces = geo.layers.backplot.selectAll('.trace.choropleth'); + + gTraces.each(function(calcTrace) { + var trace = calcTrace[0].trace; + var marker = trace.marker || {}; + var markerLine = marker.line || {}; var sclFunc = Colorscale.makeColorScaleFunc( Colorscale.extractScale( @@ -122,11 +193,11 @@ function style(geo) { ) ); - s.selectAll('path.choroplethlocation').each(function(pt) { + d3.select(this).selectAll('.choroplethlocation').each(function(d) { d3.select(this) - .attr('fill', function(pt) { return sclFunc(pt.z); }) - .call(Color.stroke, pt.mlc || markerLine.color) - .call(Drawing.dashLine, '', pt.mlw || markerLine.width || 0); + .attr('fill', sclFunc(d.z)) + .call(Color.stroke, d.mlc || markerLine.color) + .call(Drawing.dashLine, '', d.mlw || markerLine.width || 0); }); }); } diff --git a/src/traces/choropleth/select.js b/src/traces/choropleth/select.js new file mode 100644 index 00000000000..209b1ba35a7 --- /dev/null +++ b/src/traces/choropleth/select.js @@ -0,0 +1,54 @@ +/** +* Copyright 2012-2017, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; + +module.exports = function selectPoints(searchInfo, polygon) { + var cd = searchInfo.cd; + var xa = searchInfo.xaxis; + var ya = searchInfo.yaxis; + var selection = []; + var node3 = cd[0].node3; + + var i, di, ct, x, y; + + if(polygon === false) { + for(i = 0; i < cd.length; i++) { + cd[i].dim = 0; + } + } else { + for(i = 0; i < cd.length; i++) { + di = cd[i]; + ct = di.ct; + + if(!ct) continue; + + x = xa.c2p(ct); + y = ya.c2p(ct); + + if(polygon.contains([x, y])) { + selection.push({ + pointNumber: i, + lon: ct[0], + lat: ct[1] + }); + di.dim = 0; + } else { + di.dim = 1; + } + } + } + + node3.selectAll('path').style('opacity', function(d) { + return d.dim ? DESELECTDIM : 1; + }); + + return selection; +}; diff --git a/src/traces/contour/plot.js b/src/traces/contour/plot.js index 72d9e892229..a4f13aa1a73 100644 --- a/src/traces/contour/plot.js +++ b/src/traces/contour/plot.js @@ -86,7 +86,7 @@ function plotOne(gd, plotinfo, cd) { makeBackground(plotGroup, perimeter, contours); makeFills(plotGroup, pathinfo, perimeter, contours); makeLinesAndLabels(plotGroup, pathinfo, gd, cd[0], contours, perimeter); - clipGaps(plotGroup, plotinfo, fullLayout._defs, cd[0], perimeter); + clipGaps(plotGroup, plotinfo, fullLayout._clips, cd[0], perimeter); } function emptyPathinfo(contours, plotinfo, cd0) { @@ -281,7 +281,7 @@ function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, perimeter) { var linegroup = exports.createLines(lineContainer, showLines || showLabels, pathinfo); var lineClip = exports.createLineClip(lineContainer, clipLinesForLabels, - gd._fullLayout._defs, cd0.trace.uid); + gd._fullLayout._clips, cd0.trace.uid); var labelGroup = plotgroup.selectAll('g.contourlabels') .data(showLabels ? [0] : []); @@ -403,10 +403,10 @@ exports.createLines = function(lineContainer, makeLines, pathinfo) { return linegroup; }; -exports.createLineClip = function(lineContainer, clipLinesForLabels, defs, uid) { +exports.createLineClip = function(lineContainer, clipLinesForLabels, clips, uid) { var clipId = clipLinesForLabels ? ('clipline' + uid) : null; - var lineClip = defs.select('.clips').selectAll('#' + clipId) + var lineClip = clips.selectAll('#' + clipId) .data(clipLinesForLabels ? [0] : []); lineClip.exit().remove(); @@ -630,10 +630,10 @@ exports.drawLabels = function(labelGroup, labelData, gd, lineClip, labelClipPath } }; -function clipGaps(plotGroup, plotinfo, defs, cd0, perimeter) { +function clipGaps(plotGroup, plotinfo, clips, cd0, perimeter) { var clipId = 'clip' + cd0.trace.uid; - var clipPath = defs.select('.clips').selectAll('#' + clipId) + var clipPath = clips.selectAll('#' + clipId) .data(cd0.trace.connectgaps ? [] : [0]); clipPath.enter().append('clipPath') .classed('contourclip', true) diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index a82ae13cd0e..4d3135d5d2a 100644 --- a/src/traces/scattergeo/hover.js +++ b/src/traces/scattergeo/hover.js @@ -17,32 +17,27 @@ var getTraceColor = require('../scatter/get_trace_color'); var attributes = require('./attributes'); -module.exports = function hoverPoints(pointData) { - var cd = pointData.cd, - trace = cd[0].trace, - xa = pointData.xa, - ya = pointData.ya, - geo = pointData.subplot; - - function c2p(lonlat) { - return geo.projection(lonlat); - } +module.exports = function hoverPoints(pointData, xval, yval) { + var cd = pointData.cd; + var trace = cd[0].trace; + var xa = pointData.xa; + var ya = pointData.ya; + var geo = pointData.subplot; + + var isLonLatOverEdges = geo.projection.isLonLatOverEdges; + var project = geo.project; function distFn(d) { var lonlat = d.lonlat; if(lonlat[0] === BADNUM) return Infinity; + if(isLonLatOverEdges(lonlat)) return Infinity; - if(geo.isLonLatOverEdges(lonlat)) return Infinity; - - var pos = c2p(lonlat); - - var xPx = xa.c2p(), - yPx = ya.c2p(); - - var dx = Math.abs(xPx - pos[0]), - dy = Math.abs(yPx - pos[1]), - rad = Math.max(3, d.mrc || 0); + var pt = project(lonlat); + var px = project([xval, yval]); + var dx = Math.abs(pt[0] - px[0]); + var dy = Math.abs(pt[1] - px[1]); + var rad = Math.max(3, d.mrc || 0); // N.B. d.mrc is the calculated marker radius // which is only set for trace with 'markers' mode. @@ -55,10 +50,10 @@ module.exports = function hoverPoints(pointData) { // skip the rest (for this trace) if we didn't find a close point if(pointData.index === false) return; - var di = cd[pointData.index], - lonlat = di.lonlat, - pos = c2p(lonlat), - rad = di.mrc || 1; + var di = cd[pointData.index]; + var lonlat = di.lonlat; + var pos = [xa.c2p(lonlat), ya.c2p(lonlat)]; + var rad = di.mrc || 1; pointData.x0 = pos[0] - rad; pointData.x1 = pos[0] + rad; diff --git a/src/traces/scattergeo/index.js b/src/traces/scattergeo/index.js index d48f0351330..4e8c989ee33 100644 --- a/src/traces/scattergeo/index.js +++ b/src/traces/scattergeo/index.js @@ -18,11 +18,12 @@ ScatterGeo.calc = require('./calc'); ScatterGeo.plot = require('./plot'); ScatterGeo.hoverPoints = require('./hover'); ScatterGeo.eventData = require('./event_data'); +ScatterGeo.selectPoints = require('./select'); ScatterGeo.moduleType = 'trace'; ScatterGeo.name = 'scattergeo'; ScatterGeo.basePlotModule = require('../../plots/geo'); -ScatterGeo.categories = ['geo', 'symbols', 'markerColorscale', 'showLegend']; +ScatterGeo.categories = ['geo', 'symbols', 'markerColorscale', 'showLegend', 'scatter-like']; ScatterGeo.meta = { hrName: 'scatter_geo', description: [ diff --git a/src/traces/scattergeo/plot.js b/src/traces/scattergeo/plot.js index 094e2b60e84..42965cee10c 100644 --- a/src/traces/scattergeo/plot.js +++ b/src/traces/scattergeo/plot.js @@ -21,8 +21,10 @@ var locationToFeature = require('../../lib/geo_location_utils').locationToFeatur var geoJsonUtils = require('../../lib/geojson_utils'); var subTypes = require('../scatter/subtypes'); - module.exports = function plot(geo, calcData) { + for(var i = 0; i < calcData.length; i++) { + calcGeoJSON(calcData[i], geo.topojson); + } function keyFunc(d) { return d[0].trace.uid; } @@ -32,37 +34,34 @@ module.exports = function plot(geo, calcData) { } } - for(var i = 0; i < calcData.length; i++) { - fillLocationLonLat(calcData[i], geo.topojson); - } - - var gScatterGeoTraces = geo.framework.select('.scattergeolayer') + var gTraces = geo.layers.frontplot.select('.scatterlayer') .selectAll('g.trace.scattergeo') .data(calcData, keyFunc); - gScatterGeoTraces.enter().append('g') + gTraces.enter().append('g') .attr('class', 'trace scattergeo'); - gScatterGeoTraces.exit().remove(); + gTraces.exit().remove(); // TODO find a way to order the inner nodes on update - gScatterGeoTraces.selectAll('*').remove(); + gTraces.selectAll('*').remove(); - gScatterGeoTraces.each(function(calcTrace) { - var s = d3.select(this); + gTraces.each(function(calcTrace) { + var s = calcTrace[0].node3 = d3.select(this); var trace = calcTrace[0].trace; if(subTypes.hasLines(trace) || trace.fill !== 'none') { var lineCoords = geoJsonUtils.calcTraceToLineCoords(calcTrace); var lineData = (trace.fill !== 'none') ? - geoJsonUtils.makePolygon(lineCoords, trace) : - geoJsonUtils.makeLine(lineCoords, trace); + geoJsonUtils.makePolygon(lineCoords) : + geoJsonUtils.makeLine(lineCoords); s.selectAll('path.js-line') - .data([lineData]) + .data([{geojson: lineData, trace: trace}]) .enter().append('path') - .classed('js-line', true); + .classed('js-line', true) + .style('stroke-miterlimit', 2); } if(subTypes.hasMarkers(trace)) { @@ -86,7 +85,7 @@ module.exports = function plot(geo, calcData) { style(geo); }; -function fillLocationLonLat(calcTrace, topojson) { +function calcGeoJSON(calcTrace, topojson) { var trace = calcTrace[0].trace; if(!Array.isArray(trace.locations)) return; @@ -103,15 +102,15 @@ function fillLocationLonLat(calcTrace, topojson) { } function style(geo) { - var selection = geo.framework.selectAll('g.trace.scattergeo'); + var gTraces = geo.layers.frontplot.selectAll('.trace.scattergeo'); - selection.style('opacity', function(calcTrace) { + gTraces.style('opacity', function(calcTrace) { return calcTrace[0].trace.opacity; }); - selection.each(function(calcTrace) { - var trace = calcTrace[0].trace, - group = d3.select(this); + gTraces.each(function(calcTrace) { + var trace = calcTrace[0].trace; + var group = d3.select(this); group.selectAll('path.point') .call(Drawing.pointStyle, trace, geo.graphDiv); @@ -120,12 +119,12 @@ function style(geo) { }); // this part is incompatible with Drawing.lineGroupStyle - selection.selectAll('path.js-line') + gTraces.selectAll('path.js-line') .style('fill', 'none') .each(function(d) { - var path = d3.select(this), - trace = d.trace, - line = trace.line || {}; + var path = d3.select(this); + var trace = d.trace; + var line = trace.line || {}; path.call(Color.stroke, line.color) .call(Drawing.dashLine, line.dash || '', line.width || 0); diff --git a/src/traces/scattergeo/select.js b/src/traces/scattergeo/select.js new file mode 100644 index 00000000000..aa06137d783 --- /dev/null +++ b/src/traces/scattergeo/select.js @@ -0,0 +1,63 @@ +/** +* Copyright 2012-2017, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var subtypes = require('../scatter/subtypes'); +var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; + +module.exports = function selectPoints(searchInfo, polygon) { + var cd = searchInfo.cd; + var xa = searchInfo.xaxis; + var ya = searchInfo.yaxis; + var selection = []; + var trace = cd[0].trace; + var node3 = cd[0].node3; + + var di, lonlat, x, y, i; + + var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); + if(trace.visible !== true || hasOnlyLines) return; + + var marker = trace.marker; + var opacity = Array.isArray(marker.opacity) ? 1 : marker.opacity; + + if(polygon === false) { + for(i = 0; i < cd.length; i++) { + cd[i].dim = 0; + } + } else { + for(i = 0; i < cd.length; i++) { + di = cd[i]; + lonlat = di.lonlat; + x = xa.c2p(lonlat); + y = ya.c2p(lonlat); + + if(polygon.contains([x, y])) { + selection.push({ + pointNumber: i, + lon: lonlat[0], + lat: lonlat[1] + }); + di.dim = 0; + } else { + di.dim = 1; + } + } + } + + node3.selectAll('path.point').style('opacity', function(d) { + return ((d.mo + 1 || opacity + 1) - 1) * (d.dim ? DESELECTDIM : 1); + }); + + node3.selectAll('text').style('opacity', function(d) { + return d.dim ? DESELECTDIM : 1; + }); + + return selection; +}; diff --git a/test/image/baselines/geo_across-antimeridian.png b/test/image/baselines/geo_across-antimeridian.png new file mode 100644 index 00000000000..c54950d9c2d Binary files /dev/null and b/test/image/baselines/geo_across-antimeridian.png differ diff --git a/test/image/baselines/geo_africa-insets.png b/test/image/baselines/geo_africa-insets.png index 8af3269d1d5..7ccb92d4d3f 100644 Binary files a/test/image/baselines/geo_africa-insets.png and b/test/image/baselines/geo_africa-insets.png differ diff --git a/test/image/baselines/geo_aitoff-sinusoidal.png b/test/image/baselines/geo_aitoff-sinusoidal.png index a2ff63b68db..f1c6316363a 100644 Binary files a/test/image/baselines/geo_aitoff-sinusoidal.png and b/test/image/baselines/geo_aitoff-sinusoidal.png differ diff --git a/test/image/baselines/geo_bg-color.png b/test/image/baselines/geo_bg-color.png index 8421cd959f1..4ac712167d8 100644 Binary files a/test/image/baselines/geo_bg-color.png and b/test/image/baselines/geo_bg-color.png differ diff --git a/test/image/baselines/geo_big-frame.png b/test/image/baselines/geo_big-frame.png index cea96bd27b7..841380a025a 100644 Binary files a/test/image/baselines/geo_big-frame.png and b/test/image/baselines/geo_big-frame.png differ diff --git a/test/image/baselines/geo_bubbles-colorscales.png b/test/image/baselines/geo_bubbles-colorscales.png index 642da7d688c..ffe6a751441 100644 Binary files a/test/image/baselines/geo_bubbles-colorscales.png and b/test/image/baselines/geo_bubbles-colorscales.png differ diff --git a/test/image/baselines/geo_bubbles-sizeref.png b/test/image/baselines/geo_bubbles-sizeref.png index 7c2e297f822..cd82b00a59e 100644 Binary files a/test/image/baselines/geo_bubbles-sizeref.png and b/test/image/baselines/geo_bubbles-sizeref.png differ diff --git a/test/image/baselines/geo_canadian-cites.png b/test/image/baselines/geo_canadian-cites.png index a86ddf60375..560c7423061 100644 Binary files a/test/image/baselines/geo_canadian-cites.png and b/test/image/baselines/geo_canadian-cites.png differ diff --git a/test/image/baselines/geo_centering.png b/test/image/baselines/geo_centering.png new file mode 100644 index 00000000000..8e65a0e8163 Binary files /dev/null and b/test/image/baselines/geo_centering.png differ diff --git a/test/image/baselines/geo_choropleth-text.png b/test/image/baselines/geo_choropleth-text.png index f99718126de..d3c5c83d4a5 100644 Binary files a/test/image/baselines/geo_choropleth-text.png and b/test/image/baselines/geo_choropleth-text.png differ diff --git a/test/image/baselines/geo_choropleth-usa.png b/test/image/baselines/geo_choropleth-usa.png index 907be08490a..a8a084894aa 100644 Binary files a/test/image/baselines/geo_choropleth-usa.png and b/test/image/baselines/geo_choropleth-usa.png differ diff --git a/test/image/baselines/geo_conic-conformal.png b/test/image/baselines/geo_conic-conformal.png index 6b22d992168..af9247a1d9e 100644 Binary files a/test/image/baselines/geo_conic-conformal.png and b/test/image/baselines/geo_conic-conformal.png differ diff --git a/test/image/baselines/geo_connectgaps.png b/test/image/baselines/geo_connectgaps.png index 8526ed17636..f1d4ad2398d 100644 Binary files a/test/image/baselines/geo_connectgaps.png and b/test/image/baselines/geo_connectgaps.png differ diff --git a/test/image/baselines/geo_country-names-text-chart.png b/test/image/baselines/geo_country-names-text-chart.png index 76b36ed7517..b921e1e3dfc 100644 Binary files a/test/image/baselines/geo_country-names-text-chart.png and b/test/image/baselines/geo_country-names-text-chart.png differ diff --git a/test/image/baselines/geo_country-names.png b/test/image/baselines/geo_country-names.png index 156e4d175e6..b2524b79c5e 100644 Binary files a/test/image/baselines/geo_country-names.png and b/test/image/baselines/geo_country-names.png differ diff --git a/test/image/baselines/geo_custom-colorscale.png b/test/image/baselines/geo_custom-colorscale.png index 1d21bef9104..bb8652ce625 100644 Binary files a/test/image/baselines/geo_custom-colorscale.png and b/test/image/baselines/geo_custom-colorscale.png differ diff --git a/test/image/baselines/geo_europe-bubbles.png b/test/image/baselines/geo_europe-bubbles.png index cdcf55fe787..d474421526c 100644 Binary files a/test/image/baselines/geo_europe-bubbles.png and b/test/image/baselines/geo_europe-bubbles.png differ diff --git a/test/image/baselines/geo_fill.png b/test/image/baselines/geo_fill.png index c3941d6d1af..62acf49e8db 100644 Binary files a/test/image/baselines/geo_fill.png and b/test/image/baselines/geo_fill.png differ diff --git a/test/image/baselines/geo_first.png b/test/image/baselines/geo_first.png index 3929fca1fad..1fdff452c88 100644 Binary files a/test/image/baselines/geo_first.png and b/test/image/baselines/geo_first.png differ diff --git a/test/image/baselines/geo_kavrayskiy7.png b/test/image/baselines/geo_kavrayskiy7.png index 4647107c56e..9ced1a0afd4 100644 Binary files a/test/image/baselines/geo_kavrayskiy7.png and b/test/image/baselines/geo_kavrayskiy7.png differ diff --git a/test/image/baselines/geo_legendonly.png b/test/image/baselines/geo_legendonly.png index 4eff55d0d65..d9595eaa5ec 100644 Binary files a/test/image/baselines/geo_legendonly.png and b/test/image/baselines/geo_legendonly.png differ diff --git a/test/image/baselines/geo_miterlimit-base-layers.png b/test/image/baselines/geo_miterlimit-base-layers.png new file mode 100644 index 00000000000..d14040b2c7c Binary files /dev/null and b/test/image/baselines/geo_miterlimit-base-layers.png differ diff --git a/test/image/baselines/geo_multi-geos.png b/test/image/baselines/geo_multi-geos.png index 6f0e5b43a5e..de0366ab098 100644 Binary files a/test/image/baselines/geo_multi-geos.png and b/test/image/baselines/geo_multi-geos.png differ diff --git a/test/image/baselines/geo_multiple-usa-choropleths.png b/test/image/baselines/geo_multiple-usa-choropleths.png index f98da60820b..bfd37cca985 100644 Binary files a/test/image/baselines/geo_multiple-usa-choropleths.png and b/test/image/baselines/geo_multiple-usa-choropleths.png differ diff --git a/test/image/baselines/geo_orthographic.png b/test/image/baselines/geo_orthographic.png index 9cb6ffe315e..00ceee11086 100644 Binary files a/test/image/baselines/geo_orthographic.png and b/test/image/baselines/geo_orthographic.png differ diff --git a/test/image/baselines/geo_scattergeo-locations.png b/test/image/baselines/geo_scattergeo-locations.png index 6724ddebbac..0de614431f0 100644 Binary files a/test/image/baselines/geo_scattergeo-locations.png and b/test/image/baselines/geo_scattergeo-locations.png differ diff --git a/test/image/baselines/geo_scattergeo-out-of-usa.png b/test/image/baselines/geo_scattergeo-out-of-usa.png new file mode 100644 index 00000000000..aa8b27212b0 Binary files /dev/null and b/test/image/baselines/geo_scattergeo-out-of-usa.png differ diff --git a/test/image/baselines/geo_second.png b/test/image/baselines/geo_second.png index 7e7da69d10b..93c2dcb394d 100644 Binary files a/test/image/baselines/geo_second.png and b/test/image/baselines/geo_second.png differ diff --git a/test/image/baselines/geo_stereographic.png b/test/image/baselines/geo_stereographic.png index 026f06e0e77..a77239e0fd9 100644 Binary files a/test/image/baselines/geo_stereographic.png and b/test/image/baselines/geo_stereographic.png differ diff --git a/test/image/baselines/geo_text_chart_arrays.png b/test/image/baselines/geo_text_chart_arrays.png index fdd95f847b4..fc74c91f82e 100644 Binary files a/test/image/baselines/geo_text_chart_arrays.png and b/test/image/baselines/geo_text_chart_arrays.png differ diff --git a/test/image/baselines/geo_usa-states.png b/test/image/baselines/geo_usa-states.png index 06c5c681ea4..e162702a55a 100644 Binary files a/test/image/baselines/geo_usa-states.png and b/test/image/baselines/geo_usa-states.png differ diff --git a/test/image/baselines/geo_winkel-tripel.png b/test/image/baselines/geo_winkel-tripel.png index e0e864fdab9..7ad7d616bd6 100644 Binary files a/test/image/baselines/geo_winkel-tripel.png and b/test/image/baselines/geo_winkel-tripel.png differ diff --git a/test/image/baselines/plot_types.png b/test/image/baselines/plot_types.png index b143904d672..765645e7924 100644 Binary files a/test/image/baselines/plot_types.png and b/test/image/baselines/plot_types.png differ diff --git a/test/image/mocks/geo_across-antimeridian.json b/test/image/mocks/geo_across-antimeridian.json new file mode 100644 index 00000000000..22508f86e40 --- /dev/null +++ b/test/image/mocks/geo_across-antimeridian.json @@ -0,0 +1,103 @@ +{ + "data": [ + { + "type": "scattergeo", + "mode": "markers+lines", + "lon": [170, 180, -180, -170], + "lat": [-50, -45, -10, -20.5], + "marker": { "size": 20 } + }, + { + "type": "choropleth", + "locations": ["NZL", "AUS"], + "z": [0, 1], + "showscale": false + }, + { + "type": "scattergeo", + "mode": "markers+lines", + "lon": [-170, 170], + "lat": [-50, -20.5], + "marker": { "size": 20 }, + "line": { "width": 5 }, + "geo": "geo2", + "name": "west to east" + }, + { + "type": "scattergeo", + "mode": "markers+lines", + "lon": [170, -170], + "lat": [-50, -20.5], + "marker": { "size": 20 }, + "line": { "width": 5 }, + "geo": "geo2", + "name": "east to west" + }, + { + "type": "choropleth", + "locations": ["NZL", "AUS"], + "z": [0, 1], + "showscale": false, + "geo": "geo2" + } + ], + "layout": { + "geo": { + "domain": { + "x": [0, 0.5], + "y": [0, 1] + }, + "type": "miller", + "showocean": true, + "lonaxis": { + "showgrid": true, + "dtick": 2, + "range": [120, -120] + }, + "projection": { + "scale": 2 + }, + "center": { + "lat": -45 + } + }, + "geo2": { + "domain": { + "x": [0.5, 1], + "y": [0, 1] + }, + "type": "miller", + "showocean": true, + "lonaxis": { + "showgrid": true, + "dtick": 2, + "range": [150, -150] + }, + "lataxis": { + "range": [-90, 0] + } + }, + "annotations": [{ + "showarrow": false, + "xref": "paper", + "yref": "paper", + "x": 0.25, + "y": 1, + "xanchor": "center", + "yanchor": "bottom", + "text": "set lonaxis.range, center.lon and projection.scale" + }, { + "showarrow": false, + "xref": "paper", + "yref": "paper", + "x": 0.75, + "y": 1, + "xanchor": "center", + "yanchor": "bottom", + "text": "set lonaxis.range and lataxis.range" + }], + "showlegend": false, + "width": 700, + "height": 440 + } +} diff --git a/test/image/mocks/geo_canadian-cites.json b/test/image/mocks/geo_canadian-cites.json index bedc324a9d3..16f6ce6a611 100644 --- a/test/image/mocks/geo_canadian-cites.json +++ b/test/image/mocks/geo_canadian-cites.json @@ -95,6 +95,9 @@ 70 ] }, + "center": { + "lat": 57 + }, "showrivers": true, "rivercolor": "#fff", "showlakes": true, diff --git a/test/image/mocks/geo_centering.json b/test/image/mocks/geo_centering.json new file mode 100644 index 00000000000..6bcae0b9ea9 --- /dev/null +++ b/test/image/mocks/geo_centering.json @@ -0,0 +1,41 @@ +{ + "data": [{ + "type": "scattergeo", + "mode": "markers", + "marker": { + "size": 9, + "color": "#154360" + }, + "lat": [44.065041, 42.012902], + "lon": [-82.649583, -82.545763] + }], + "layout": { + "margin": {"t": 70, "b": 40, "l": 10, "r": 10}, + "geo": { + "projection": { + "type": "kavrayskiy7", + "rotation":{"lon": -81.6}, + "scale": 6 + }, + "center": { + "lat": 39 + }, + "showcountries": true, + "showland": true, + "showsubunits": true, + "lataxis": { + "showgrid": true, + "tick0": 3, + "dtick": 3 + }, + "lonaxis": { + "showgrid": true, + "tick0": 3, + "dtick": 3 + }, + "landcolor": "rgb(243,243,243)", + "countrycolor": "rgb(204,204,204)", + "subunitcolor": "rgb(224,224,224)" + } + } +} diff --git a/test/image/mocks/geo_fill.json b/test/image/mocks/geo_fill.json index 12d749caca7..3dc3a388574 100644 --- a/test/image/mocks/geo_fill.json +++ b/test/image/mocks/geo_fill.json @@ -91,7 +91,8 @@ "layout": { "geo": { "projection": { - "type": "natural earth" + "type": "natural earth", + "rotation": {"lon": 0} }, "lonaxis": { "range": [-80, -65] @@ -99,6 +100,9 @@ "lataxis": { "range": [42, 51] }, + "center": { + "lon": -72.5 + }, "showland": true }, "showlegend": false, diff --git a/test/image/mocks/geo_miterlimit-base-layers.json b/test/image/mocks/geo_miterlimit-base-layers.json new file mode 100644 index 00000000000..a90c9344323 --- /dev/null +++ b/test/image/mocks/geo_miterlimit-base-layers.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "type": "scattergeo", + "lon": [-72], + "lat": [40], + "marker": { + "size": 20, + "color": "#d3d3d3" + } + } + ], + "layout": { + "geo": { + "projection": { + "type": "orthographic", + "rotation": { + "lon": -72, + "lat": 40 + }, + "scale": 15 + }, + "showcoastlines": true, + "showcountries": true, + "showland": true, + "showocean": true, + "countrywidth": 8 + }, + "width": 500, + "height": 500 + } +} diff --git a/test/image/mocks/geo_scattergeo-out-of-usa.json b/test/image/mocks/geo_scattergeo-out-of-usa.json new file mode 100644 index 00000000000..3118256d18d --- /dev/null +++ b/test/image/mocks/geo_scattergeo-out-of-usa.json @@ -0,0 +1,20070 @@ +{ + "data": [ + { + "uid": "c7d814", + "lon": [ + -113, + -66, + -113, + -114, + -111, + -111, + -109, + -115, + -113, + -114, + -67, + -112, + -65, + -112, + -113, + -105, + -112, + -112, + -109, + -107, + -112, + -113, + -66, + -114, + -114, + -108, + -108, + -116, + -107, + -65, + -113, + -66, + -107, + -113, + -113, + -112, + -112, + -114, + -110, + -111, + -110, + -112, + -108, + -113, + -109, + -82, + -115, + -66, + -111, + -112, + -109, + -113, + -111, + -111, + -110, + -112, + -114, + -113, + -109, + -111, + -105, + -109, + -110, + -103, + -112, + -113, + -110, + -109, + -110, + -107, + -103, + -111, + -109, + -110, + -101, + -111, + -115, + -114, + -112, + -103, + -98, + -111, + -112, + -113, + -112, + -115, + -111, + -115, + -113, + -114, + -111, + -113, + -109, + -110, + -112, + -106, + -114, + -65, + -112, + -66, + -111, + -115, + -115, + -107, + -105, + -108, + -111, + -114, + -107, + -115, + -100, + -113, + -114, + -104, + -111, + -116, + -106, + -116, + -115, + -110, + -103, + -113, + -114, + -106, + -109, + -115, + -114, + -115, + -91, + -111, + -104, + -104, + -114, + -114, + -114, + -114, + -112, + -82, + -107, + -114, + -114, + -66, + -110, + -114, + -104, + -112, + -104, + -109, + -111, + -114, + -110, + -100, + -105, + -106, + -101, + -114, + -113, + -102, + -112, + -109, + -107, + -89, + -108, + -82, + -106, + -107, + -108, + -111, + -109, + -114, + -106, + -106, + -93, + -115, + -66, + -102, + -66, + -103, + -108, + -102, + -105, + -105, + -106, + -112, + -113, + -115, + -107, + -115, + -105, + -116, + -115, + -104, + -82, + -112, + -113, + -100, + -118, + -103, + -108, + -104, + -102, + -81, + -115, + -86, + -104, + -105, + -102, + -88, + -107, + -103, + -107, + -111, + -103, + -95, + -101, + -103, + -113, + -112, + -106, + -101, + -105, + -112, + -105, + -114, + -113, + -92, + -112, + -114, + -109, + -99, + -116, + -115, + -114, + -93, + -103, + -92, + -104, + -116, + -103, + -99, + -108, + -105, + -104, + -97, + -113, + -103, + -92, + -106, + -103, + -109, + -93, + -95, + -110, + -104, + -109, + -112, + -105, + -107, + -111, + -102, + -114, + -80, + -115, + -107, + -105, + -98, + -106, + -106, + -89, + -115, + -114, + -104, + -93, + -86, + -106, + -106, + -112, + -104, + -108, + -116, + -90, + -101, + -100, + -114, + -115, + -65, + -114, + -112, + -94, + -83, + -115, + -111, + -116, + -104, + -107, + -115, + -105, + -95, + -104, + -101, + -114, + -114, + -105, + -112, + -117, + -102, + -114, + -84, + -113, + -93, + -106, + -114, + -82, + -105, + -110, + -105, + -106, + -67, + -98, + -118, + -66, + -95, + -107, + -85, + -106, + -103, + -105, + -103, + -101, + -90, + -93, + -100, + -104, + -112, + -86, + -104, + -105, + -106, + -107, + -104, + -111, + -116, + -91, + -98, + -85, + -104, + -101, + -88, + -96, + -85, + -66, + -108, + -104, + -102, + -96, + -100, + -82, + -109, + -113, + -111, + -97, + -104, + -99, + -105, + -96, + -95, + -103, + -107, + -105, + -117, + -115, + -81, + -106, + -102, + -82, + -110, + -66, + -105, + -106, + -107, + -105, + -83, + -107, + -92, + -100, + -85, + -108, + -104, + -87, + -81, + -104, + -103, + -86, + -100, + -107, + -102, + -115, + -91, + -98, + -104, + -81, + -104, + -115, + -116, + -90, + -100, + -114, + -107, + -106, + -101, + -100, + -114, + -103, + -98, + -104, + -96, + -103, + -102, + -119, + -86, + -105, + -86, + -83, + -91, + -108, + -103, + -115, + -104, + -105, + -104, + -84, + -95, + -106, + -101, + -102, + -101, + -102, + -87, + -91, + -114, + -104, + -92, + -106, + -106, + -97, + -105, + -115, + -98, + -101, + -108, + -101, + -117, + -86, + -67, + -103, + -98, + -81, + -103, + -84, + -98, + -98, + -91, + -89, + -100, + -88, + -104, + -109, + -83, + -104, + -82, + -109, + -94, + -101, + -95, + -88, + -101, + -101, + -100, + -101, + -99, + -109, + -95, + -105, + -109, + -86, + -66, + -102, + -104, + -101, + -84, + -94, + -102, + -100, + -85, + -100, + -98, + -95, + -105, + -105, + -101, + -113, + -105, + -116, + -85, + -103, + -114, + -95, + -114, + -103, + -102, + -82, + -101, + -89, + -100, + -97, + -84, + -87, + -97, + -102, + -95, + -101, + -97, + -107, + -103, + -105, + -94, + -104, + -108, + -84, + -97, + -112, + -106, + -106, + -93, + -105, + -84, + -100, + -100, + -103, + -97, + -89, + -94, + -106, + -93, + -101, + -101, + -100, + -113, + -104, + -102, + -101, + -102, + -97, + -100, + -102, + -97, + -106, + -101, + -82, + -118, + -100, + -87, + -84, + -115, + -107, + -106, + -100, + -109, + -97, + -85, + -104, + -103, + -100, + -100, + -100, + -102, + -90, + -102, + -93, + -83, + -89, + -115, + -113, + -102, + -100, + -98, + -105, + -117, + -106, + -99, + -99, + -105, + -85, + -107, + -85, + -105, + -99, + -96, + -115, + -104, + -108, + -83, + -105, + -98, + -100, + -114, + -85, + -83, + -87, + -102, + -103, + -114, + -103, + -102, + -100, + -103, + -116, + -100, + -101, + -117, + -105, + -100, + -103, + -100, + -104, + -107, + -93, + -103, + -116, + -93, + -97, + -85, + -103, + -101, + -85, + -88, + -114, + -83, + -81, + -104, + -84, + -98, + -104, + -84, + -103, + -105, + -80, + -102, + -81, + -82, + -98, + -94, + -102, + -93, + -100, + -100, + -100, + -90, + -100, + -86, + -101, + -92, + -86, + -109, + -106, + -107, + -99, + -103, + -86, + -102, + -107, + -108, + -101, + -101, + -84, + -89, + -96, + -113, + -95, + -101, + -102, + -105, + -103, + -82, + -100, + -84, + -83, + -82, + -100, + -100, + -102, + -88, + -95, + -102, + -101, + -84, + -107, + -105, + -104, + -101, + -104, + -98, + -93, + -86, + -95, + -103, + -83, + -97, + -102, + -99, + -108, + -116, + -103, + -89, + -117, + -117, + -83, + -96, + -100, + -100, + -90, + -100, + -87, + -100, + -83, + -104, + -106, + -104, + -99, + -84, + -103, + -88, + -112, + -101, + -100, + -110, + -98, + -82, + -86, + -102, + -105, + -104, + -94, + -83, + -95, + -100, + -84, + -102, + -103, + -104, + -114, + -104, + -100, + -103, + -81, + -103, + -107, + -91, + -103, + -97, + -104, + -104, + -103, + -84, + -102, + -106, + -98, + -94, + -99, + -96, + -116, + -102, + -84, + -101, + -94, + -82, + -84, + -106, + -99, + -97, + -109, + -106, + -99, + -94, + -113, + -87, + -89, + -97, + -106, + -87, + -105, + -104, + -101, + -83, + -91, + -90, + -101, + -106, + -92, + -104, + -102, + -85, + -90, + -104, + -98, + -95, + -101, + -101, + -99, + -94, + -82, + -99, + -84, + -90, + -94, + -98, + -96, + -100, + -85, + -114, + -83, + -102, + -83, + -84, + -91, + -118, + -83, + -100, + -97, + -101, + -85, + -93, + -97, + -109, + -100, + -105, + -91, + -88, + -98, + -91, + -86, + -98, + -84, + -98, + -85, + -104, + -87, + -102, + -85, + -91, + -85, + -87, + -114, + -101, + -105, + -104, + -100, + -114, + -112, + -106, + -99, + -83, + -100, + -97, + -86, + -104, + -95, + -82, + -98, + -116, + -102, + -109, + -101, + -101, + -86, + -101, + -99, + -102, + -84, + -115, + -90, + -96, + -82, + -103, + -90, + -100, + -94, + -98, + -95, + -84, + -114, + -99, + -101, + -115, + -102, + -84, + -102, + -81, + -82, + -93, + -104, + -106, + -105, + -100, + -86, + -99, + -88, + -86, + -103, + -101, + -89, + -98, + -91, + -88, + -100, + -103, + -107, + -100, + -96, + -100, + -101, + -105, + -102, + -85, + -83, + -96, + -100, + -103, + -106, + -89, + -111, + -98, + -89, + -82, + -104, + -103, + -96, + -100, + -103, + -102, + -99, + -100, + -104, + -93, + -95, + -103, + -104, + -83, + -86, + -99, + -99, + -81, + -101, + -116, + -101, + -113, + -106, + -106, + -86, + -100, + -97, + -83, + -114, + -83, + -83, + -84, + -101, + -102, + -99, + -83, + -103, + -85, + -92, + -88, + -100, + -101, + -99, + -97, + -88, + -99, + -94, + -96, + -107, + -94, + -102, + -104, + -86, + -104, + -86, + -102, + -84, + -93, + -96, + -90, + -87, + -87, + -104, + -105, + -96, + -104, + -103, + -83, + -100, + -88, + -102, + -105, + -89, + -84, + -105, + -103, + -85, + -83, + -98, + -97, + -100, + -100, + -97, + -100, + -100, + -97, + -102, + -85, + -104, + -66, + -100, + -104, + -96, + -85, + -101, + -103, + -82, + -101, + -87, + -95, + -92, + -97, + -100, + -103, + -86, + -99, + -100, + -94, + -91, + -98, + -99, + -93, + -85, + -102, + -100, + -112, + -102, + -105, + -99, + -114, + -95, + -94, + -83, + -99, + -104, + -84, + -97, + -84, + -93, + -101, + -100, + -91, + -82, + -103, + -83, + -98, + -100, + -104, + -100, + -101, + -96, + -87, + -81, + -95, + -98, + -104, + -91, + -89, + -88, + -101, + -87, + -98, + -96, + -102, + -86, + -90, + -98, + -90, + -114, + -91, + -102, + -84, + -99, + -111, + -84, + -98, + -102, + -100, + -86, + -85, + -101, + -106, + -101, + -84, + -85, + -99, + -100, + -90, + -85, + -83, + -80, + -105, + -96, + -83, + -93, + -104, + -103, + -83, + -100, + -102, + -90, + -84, + -105, + -101, + -96, + -95, + -101, + -93, + -92, + -103, + -99, + -83, + -99, + -89, + -96, + -95, + -66, + -85, + -90, + -83, + -95, + -103, + -85, + -101, + -101, + -91, + -96, + -100, + -96, + -105, + -96, + -105, + -97, + -84, + -86, + -91, + -88, + -89, + -112, + -104, + -87, + -94, + -84, + -94, + -92, + -86, + -101, + -103, + -94, + -99, + -85, + -114, + -97, + -97, + -96, + -100, + -94, + -96, + -93, + -83, + -87, + -88, + -95, + -102, + -113, + -101, + -99, + -107, + -100, + -95, + -103, + -101, + -100, + -105, + -100, + -96, + -97, + -117, + -89, + -91, + -104, + -98, + -99, + -103, + -99, + -104, + -104, + -98, + -84, + -101, + -91, + -95, + -93, + -86, + -85, + -105, + -84, + -83, + -103, + -101, + -100, + -84, + -97, + -86, + -88, + -84, + -96, + -114, + -100, + -99, + -95, + -100, + -84, + -88, + -102, + -102, + -83, + -97, + -87, + -104, + -93, + -104, + -106, + -80, + -84, + -104, + -99, + -85, + -115, + -115, + -101, + -85, + -97, + -99, + -99, + -115, + -101, + -85, + -94, + -84, + -98, + -107, + -96, + -101, + -100, + -104, + -101, + -102, + -102, + -99, + -85, + -98, + -104, + -105, + -105, + -85, + -97, + -115, + -99, + -99, + -101, + -97, + -100, + -105, + -84, + -86, + -87, + -89, + -93, + -83, + -85, + -101, + -99, + -101, + -100, + -85, + -90, + -84, + -96, + -103, + -89, + -98, + -86, + -102, + -87, + -100, + -90, + -91, + -101, + -114, + -104, + -104, + -97, + -83, + -96, + -98, + -83, + -91, + -98, + -86, + -103, + -100, + -88, + -85, + -89, + -112, + -99, + -113, + -84, + -100, + -102, + -88, + -84, + -106, + -96, + -94, + -108, + -113, + -85, + -103, + -84, + -81, + -103, + -92, + -101, + -84, + -91, + -100, + -90, + -79, + -81, + -88, + -97, + -104, + -84, + -98, + -86, + -97, + -86, + -101, + -92, + -105, + -97, + -87, + -97, + -97, + -84, + -84, + -113, + -115, + -102, + -104, + -100, + -103, + -101, + -88, + -87, + -101, + -103, + -106, + -113, + -95, + -101, + -99, + -98, + -99, + -100, + -88, + -97, + -103, + -95, + -103, + -85, + -106, + -90, + -97, + -87, + -108, + -100, + -83, + -81, + -101, + -97, + -104, + -85, + -105, + -97, + -92, + -84, + -104, + -89, + -113, + -90, + -103, + -105, + -100, + -85, + -93, + -103, + -88, + -81, + -97, + -102, + -98, + -88, + -108, + -92, + -116, + -98, + -116, + -97, + -91, + -101, + -97, + -99, + -88, + -90, + -91, + -100, + -92, + -95, + -86, + -96, + -88, + -99, + -100, + -85, + -87, + -98, + -88, + -99, + -99, + -87, + -103, + -84, + -90, + -87, + -100, + -99, + -96, + -100, + -99, + -115, + -107, + -114, + -91, + -85, + -105, + -100, + -99, + -82, + -87, + -84, + -100, + -92, + -96, + -83, + -104, + -113, + -102, + -95, + -102, + -115, + -84, + -93, + -96, + -116, + -94, + -111, + -103, + -104, + -97, + -112, + -102, + -101, + -89, + -82, + -84, + -88, + -103, + -84, + -87, + -84, + -89, + -97, + -96, + -90, + -97, + -89, + -100, + -115, + -102, + -88, + -91, + -90, + -96, + -104, + -100, + -84, + -83, + -94, + -98, + -97, + -83, + -87, + -97, + -96, + -106, + -98, + -97, + -87, + -82, + -97, + -103, + -101, + -101, + -104, + -113, + -84, + -101, + -98, + -98, + -100, + -90, + -87, + -100, + -91, + -93, + -90, + -97, + -95, + -102, + -113, + -89, + -84, + -106, + -95, + -115, + -103, + -92, + -89, + -98, + -84, + -105, + -101, + -105, + -85, + -103, + -100, + -85, + -91, + -85, + -98, + -97, + -106, + -100, + -100, + -89, + -83, + -94, + -103, + -94, + -87, + -86, + -93, + -86, + -97, + -85, + -112, + -100, + -114, + -101, + -99, + -98, + -92, + -100, + -104, + -87, + -106, + -100, + -86, + -82, + -86, + -101, + -101, + -90, + -85, + -101, + -99, + -98, + -99, + -92, + -102, + -101, + -115, + -100, + -102, + -89, + -100, + -114, + -88, + -84, + -101, + -86, + -85, + -85, + -101, + -90, + -99, + -92, + -87, + -96, + -91, + -102, + -104, + -82, + -87, + -85, + -97, + -98, + -92, + -93, + -108, + -85, + -103, + -85, + -102, + -99, + -86, + -98, + -103, + -82, + -102, + -100, + -104, + -102, + -99, + -85, + -104, + -99, + -80, + -84, + -100, + -99, + -87, + -100, + -84, + -87, + -84, + -105, + -101, + -102, + -84, + -96, + -92, + -84, + -86, + -90, + -91, + -85, + -106, + -88, + -85, + -101, + -85, + -99, + -98, + -102, + -92, + -99, + -97, + -84, + -83, + -102, + -101, + -100, + -99, + -99, + -95, + -103, + -92, + -85, + -99, + -104, + -92, + -83, + -80, + -98, + -85, + -86, + -97, + -94, + -98, + -85, + -105, + -98, + -87, + -97, + -85, + -114, + -94, + -98, + -86, + -108, + -86, + -96, + -98, + -104, + -94, + -104, + -98, + -83, + -93, + -98, + -96, + -99, + -99, + -97, + -91, + -87, + -102, + -103, + -101, + -99, + -89, + -83, + -101, + -82, + -103, + -87, + -95, + -100, + -85, + -101, + -103, + -103, + -91, + -83, + -99, + -83, + -96, + -82, + -101, + -83, + -109, + -82, + -98, + -88, + -81, + -114, + -103, + -98, + -105, + -106, + -81, + -90, + -103, + -87, + -82, + -96, + -82, + -93, + -95, + -82, + -100, + -100, + -96, + -89, + -95, + -94, + -97, + -84, + -99, + -102, + -87, + -100, + -111, + -88, + -99, + -100, + -100, + -94, + -83, + -96, + -93, + -101, + -91, + -101, + -100, + -98, + -101, + -90, + -100, + -116, + -85, + -98, + -84, + -96, + -87, + -101, + -101, + -114, + -94, + -95, + -99, + -102, + -82, + -85, + -89, + -84, + -112, + -97, + -96, + -97, + -95, + -103, + -101, + -86, + -100, + -83, + -83, + -98, + -85, + -99, + -103, + -91, + -98, + -87, + -102, + -105, + -84, + -87, + -93, + -83, + -97, + -82, + -84, + -90, + -91, + -98, + -100, + -97, + -91, + -87, + -102, + -93, + -100, + -99, + -98, + -103, + -92, + -101, + -82, + -86, + -104, + -98, + -106, + -85, + -102, + -98, + -89, + -104, + -81, + -100, + -89, + -91, + -112, + -101, + -100, + -89, + -94, + -93, + -105, + -91, + -83, + -88, + -99, + -98, + -104, + -91, + -89, + -90, + -85, + -98, + -115, + -101, + -99, + -87, + -83, + -95, + -87, + -88, + -93, + -85, + -116, + -102, + -98, + -86, + -97, + -99, + -87, + -115, + -91, + -115, + -103, + -93, + -97, + -97, + -96, + -114, + -105, + -91, + -85, + -82, + -101, + -80, + -84, + -86, + -88, + -99, + -93, + -115, + -95, + -94, + -90, + -98, + -94, + -102, + -96, + -86, + -82, + -85, + -102, + -89, + -102, + -88, + -115, + -100, + -87, + -97, + -103, + -89, + -85, + -96, + -102, + -100, + -96, + -84, + -94, + -94, + -103, + -98, + -100, + -85, + -99, + -100, + -94, + -91, + -97, + -82, + -105, + -83, + -85, + -86, + -97, + -85, + -103, + -80, + -96, + -87, + -82, + -100, + -95, + -96, + -102, + -94, + -100, + -106, + -104, + -86, + -90, + -86, + -102, + -89, + -88, + -87, + -88, + -83, + -84, + -87, + -99, + -94, + -90, + -84, + -94, + -103, + -83, + -89, + -90, + -89, + -104, + -89, + -88, + -101, + -85, + -85, + -91, + -84, + -98, + -112, + -116, + -115, + -98, + -103, + -93, + -94, + -100, + -83, + -99, + -116, + -95, + -91, + -88, + -114, + -83, + -89, + -84, + -101, + -102, + -83, + -98, + -95, + -81, + -93, + -85, + -95, + -101, + -98, + -100, + -102, + -100, + -104, + -80, + -97, + -113, + -98, + -87, + -102, + -99, + -98, + -104, + -98, + -99, + -101, + -99, + -102, + -99, + -99, + -85, + -92, + -80, + -105, + -86, + -95, + -85, + -104, + -102, + -81, + -103, + -116, + -93, + -99, + -82, + -98, + -86, + -97, + -105, + -84, + -95, + -97, + -83, + -104, + -97, + -94, + -115, + -101, + -98, + -88, + -84, + -95, + -95, + -83, + -101, + -103, + -88, + -89, + -85, + -97, + -103, + -103, + -101, + -100, + -103, + -84, + -99, + -103, + -103, + -90, + -99, + -84, + -90, + -83, + -86, + -96, + -96, + -93, + -83, + -94, + -89, + -95, + -93, + -104, + -89, + -102, + -103, + -101, + -116, + -82, + -98, + -83, + -99, + -101, + -89, + -85, + -97, + -114, + -101, + -101, + -104, + -96, + -99, + -101, + -91, + -99, + -99, + -99, + -82, + -113, + -102, + -116, + -82, + -99, + -97, + -83, + -100, + -86, + -90, + -81, + -84, + -100, + -98, + -105, + -81, + -84, + -101, + -112, + -89, + -100, + -96, + -87, + -97, + -86, + -100, + -103, + -99, + -101, + -85, + -100, + -88, + -103, + -91, + -84, + -92, + -93, + -104, + -81, + -81, + -87, + -100, + -100, + -84, + -98, + -86, + -102, + -97, + -110, + -90, + -106, + -105, + -101, + -83, + -83, + -108, + -93, + -98, + -91, + -83, + -86, + -101, + -83, + -99, + -104, + -97, + -98, + -104, + -98, + -106, + -95, + -84, + -101, + -104, + -82, + -104, + -97, + -113, + -97, + -83, + -95, + -95, + -89, + -96, + -86, + -90, + -98, + -83, + -99, + -102, + -98, + -106, + -89, + -97, + -101, + -99, + -95, + -99, + -95, + -96, + -95, + -95, + -113, + -84, + -83, + -115, + -110, + -98, + -104, + -98, + -96, + -82, + -108, + -100, + -93, + -100, + -92, + -96, + -82, + -103, + -100, + -99, + -100, + -84, + -101, + -84, + -85, + -85, + -86, + -101, + -85, + -99, + -115, + -86, + -100, + -86, + -101, + -100, + -85, + -90, + -91, + -102, + -92, + -82, + -100, + -84, + -102, + -99, + -95, + -100, + -100, + -93, + -101, + -99, + -103, + -99, + -90, + -87, + -82, + -115, + -95, + -85, + -99, + -101, + -96, + -85, + -85, + -90, + -98, + -90, + -82, + -87, + -99, + -101, + -81, + -83, + -99, + -104, + -102, + -92, + -99, + -100, + -100, + -93, + -103, + -101, + -96, + -98, + -104, + -89, + -83, + -84, + -85, + -85, + -82, + -84, + -93, + -88, + -97, + -101, + -98, + -94, + -95, + -99, + -99, + -99, + -94, + -87, + -82, + -86, + -98, + -98, + -84, + -84, + -105, + -86, + -100, + -102, + -92, + -104, + -96, + -90, + -86, + -89, + -97, + -85, + -85, + -103, + -95, + -89, + -87, + -101, + -86, + -93, + -98, + -85, + -94, + -103, + -87, + -83, + -95, + -91, + -100, + -85, + -87, + -81, + -96, + -96, + -85, + -112, + -85, + -113, + -93, + -91, + -102, + -97, + -92, + -91, + -86, + -89, + -99, + -95, + -104, + -92, + -99, + -101, + -84, + -102, + -85, + -99, + -98, + -87, + -79, + -95, + -101, + -84, + -82, + -92, + -94, + -98, + -95, + -95, + -100, + -100, + -102, + -99, + -101, + -90, + -101, + -84, + -87, + -99, + -96, + -99, + -97, + -93, + -101, + -84, + -105, + -95, + -99, + -86, + -91, + -101, + -94, + -81, + -91, + -95, + -86, + -98, + -104, + -102, + -96, + -86, + -96, + -80, + -95, + -97, + -101, + -87, + -90, + -104, + -102, + -95, + -98, + -111, + -88, + -100, + -93, + -100, + -101, + -89, + -86, + -101, + -86, + -104, + -99, + -104, + -86, + -84, + -85, + -100, + -84, + -103, + -98, + -85, + -86, + -89, + -85, + -101, + -106, + -102, + -96, + -85, + -101, + -114, + -96, + -102, + -104, + -104, + -86, + -90, + -92, + -89, + -102, + -104, + -95, + -90, + -100, + -99, + -103, + -103, + -83, + -85, + -97, + -96, + -81, + -102, + -86, + -99, + -93, + -104, + -88, + -88, + -99, + -103, + -91, + -90, + -96, + -95, + -113, + -82, + -89, + -91, + -99, + -99, + -86, + -86, + -97, + -87, + -91, + -101, + -102, + -105, + -97, + -104, + -84, + -104, + -91, + -104, + -98, + -104, + -114, + -83, + -83, + -102, + -102, + -100, + -96, + -88, + -87, + -82, + -91, + -97, + -101, + -113, + -102, + -103, + -85, + -101, + -103, + -92, + -90, + -101, + -99, + -92, + -85, + -86, + -102, + -99, + -95, + -84, + -87, + -102, + -104, + -91, + -101, + -103, + -88, + -100, + -84, + -99, + -100, + -85, + -88, + -97, + -102, + -96, + -99, + -96, + -90, + -83, + -103, + -98, + -103, + -85, + -99, + -84, + -93, + -85, + -87, + -83, + -95, + -87, + -85, + -94, + -97, + -100, + -81, + -105, + -99, + -102, + -104, + -99, + -100, + -91, + -88, + -83, + -85, + -86, + -93, + -95, + -81, + -89, + -90, + -84, + -92, + -88, + -84, + -98, + -83, + -87, + -84, + -81, + -85, + -96, + -100, + -94, + -98, + -95, + -90, + -87, + -94, + -93, + -98, + -86, + -96, + -99, + -98, + -102, + -85, + -86, + -83, + -94, + -85, + -91, + -101, + -92, + -86, + -97, + -95, + -103, + -99, + -95, + -113, + -89, + -86, + -100, + -89, + -88, + -93, + -91, + -106, + -103, + -89, + -94, + -95, + -89, + -89, + -99, + -103, + -103, + -84, + -102, + -87, + -82, + -84, + -95, + -100, + -91, + -100, + -100, + -98, + -114, + -101, + -99, + -99, + -100, + -92, + -84, + -92, + -99, + -101, + -86, + -83, + -89, + -85, + -97, + -116, + -84, + -93, + -113, + -98, + -97, + -86, + -101, + -87, + -93, + -101, + -95, + -97, + -100, + -98, + -101, + -101, + -102, + -84, + -102, + -84, + -83, + -95, + -99, + -84, + -86, + -89, + -115, + -85, + -112, + -114, + -99, + -93, + -81, + -84, + -107, + -103, + -103, + -101, + -88, + -100, + -102, + -96, + -92, + -86, + -94, + -91, + -105, + -103, + -85, + -96, + -113, + -111, + -81, + -84, + -104, + -96, + -85, + -99, + -103, + -83, + -89, + -90, + -98, + -84, + -85, + -103, + -86, + -94, + -92, + -83, + -87, + -99, + -80, + -87, + -94, + -84, + -92, + -85, + -88, + -97, + -87, + -88, + -89, + -81, + -90, + -99, + -84, + -104, + -100, + -99, + -96, + -93, + -86, + -93, + -98, + -93, + -98, + -90, + -100, + -101, + -100, + -102, + -92, + -100, + -102, + -88, + -101, + -113, + -99, + -104, + -87, + -99, + -97, + -95, + -99, + -101, + -100, + -84, + -94, + -88, + -105, + -101, + -102, + -99, + -103, + -83, + -103, + -83, + -89, + -85, + -82, + -101, + -99, + -98, + -92, + -116, + -98, + -86, + -108, + -100, + -85, + -96, + -91, + -82, + -83, + -88, + -88, + -96, + -95, + -84, + -89, + -100, + -100, + -96, + -86, + -113, + -102, + -89, + -87, + -84, + -81, + -97, + -98, + -106, + -100, + -100, + -98, + -100, + -86, + -87, + -97, + -86, + -89, + -82, + -87, + -91, + -85, + -96, + -88, + -86, + -85, + -87, + -83, + -88, + -101, + -92, + -101, + -100, + -85, + -101, + -87, + -97, + -89, + -97, + -97, + -92, + -101, + -86, + -102, + -83, + -100, + -85, + -93, + -100, + -85, + -87, + -99, + -103, + -115, + -95, + -85, + -98, + -101, + -86, + -97, + -98, + -89, + -102, + -101, + -98, + -101, + -82, + -88, + -88, + -103, + -83, + -82, + -83, + -95, + -93, + -92, + -84, + -96, + -112, + -111, + -84, + -99, + -98, + -86, + -101, + -85, + -99, + -98, + -99, + -98, + -85, + -84, + -83, + -93, + -93, + -100, + -95, + -84, + -99, + -95, + -98, + -88, + -85, + -97, + -100, + -86, + -87, + -98, + -94, + -84, + -83, + -90, + -101, + -99, + -102, + -89, + -99, + -89, + -102, + -89, + -97, + -112, + -84, + -105, + -91, + -92, + -101, + -113, + -81, + -106, + -86, + -87, + -93, + -102, + -89, + -99, + -104, + -84, + -93, + -82, + -97, + -104, + -85, + -83, + -83, + -99, + -88, + -85, + -103, + -98, + -114, + -92, + -115, + -97, + -91, + -95, + -83, + -85, + -97, + -99, + -81, + -85, + -101, + -95, + -98, + -93, + -84, + -85, + -101, + -86, + -86, + -99, + -85, + -95, + -100, + -98, + -96, + -102, + -94, + -85, + -104, + -89, + -96, + -95, + -88, + -100, + -93, + -85, + -102, + -87, + -100, + -88, + -97, + -96, + -94, + -95, + -97, + -82, + -99, + -99, + -85, + -104, + -84, + -100, + -92, + -98, + -96, + -113, + -94, + -95, + -94, + -97, + -99, + -84, + -96, + -81, + -95, + -84, + -82, + -87, + -94, + -102, + -103, + -84, + -95, + -88, + -99, + -93, + -103, + -100, + -87, + -91, + -87, + -98, + -82, + -86, + -97, + -100, + -100, + -86, + -100, + -84, + -85, + -99, + -104, + -92, + -87, + -96, + -92, + -89, + -91, + -99, + -90, + -93, + -102, + -85, + -97, + -105, + -102, + -83, + -83, + -84, + -85, + -101, + -99, + -103, + -95, + -84, + -115, + -103, + -104, + -99, + -96, + -82, + -100, + -92, + -84, + -101, + -91, + -98, + -97, + -85, + -96, + -91, + -84, + -102, + -102, + -84, + -84, + -99, + -86, + -98, + -85, + -99, + -83, + -98, + -88, + -96, + -95, + -85, + -90, + -83, + -101, + -104, + -93, + -105, + -90, + -88, + -100, + -85, + -91, + -99, + -95, + -82, + -88, + -97, + -101, + -112, + -88, + -85, + -100, + -83, + -100, + -85, + -89, + -85, + -87, + -83, + -115, + -98, + -103, + -103, + -96, + -101, + -99, + -85, + -90, + -103, + -89, + -84, + -80, + -90, + -99, + -102, + -98, + -85, + -99, + -88, + -95, + -99, + -104, + -100, + -113, + -98, + -102, + -97, + -84, + -91, + -93, + -87, + -114, + -102, + -97, + -100, + -103, + -98, + -89, + -100, + -83, + -98, + -99, + -84, + -104, + -104, + -98, + -83, + -97, + -100, + -84, + -91, + -87, + -97, + -104, + -85, + -89, + -84, + -94, + -97, + -100, + -97, + -87, + -89, + -87, + -86, + -87, + -114, + -83, + -85, + -97, + -85, + -90, + -86, + -100, + -103, + -100, + -87, + -87, + -99, + -103, + -90, + -99, + -97, + -104, + -85, + -83, + -80, + -114, + -100, + -103, + -96, + -83, + -98, + -103, + -102, + -83, + -88, + -99, + -85, + -87, + -100, + -85, + -90, + -85, + -83, + -116, + -114, + -102, + -88, + -90, + -94, + -85, + -83, + -85, + -86, + -91, + -100, + -85, + -98, + -83, + -91, + -84, + -91, + -115, + -91, + -91, + -94, + -95, + -97, + -113, + -103, + -88, + -101, + -85, + -99, + -87, + -91, + -86, + -83, + -101, + -101, + -88, + -83, + -83, + -99, + -96, + -81, + -85, + -104, + -85, + -93, + -99, + -99, + -87, + -99, + -99, + -86, + -84, + -90, + -87, + -99, + -104, + -95, + -99, + -100, + -85, + -83, + -100, + -85, + -84, + -94, + -102, + -84, + -82, + -85, + -87, + -92, + -85, + -95, + -87, + -93, + -100, + -83, + -87, + -102, + -101, + -91, + -101, + -87, + -87, + -100, + -86, + -84, + -97, + -87, + -88, + -105, + -93, + -86, + -87, + -113, + -83, + -94, + -82, + -104, + -103, + -85, + -84, + -83, + -85, + -83, + -83, + -101, + -91, + -97, + -83, + -84, + -98, + -92, + -102, + -90, + -91, + -82, + -83, + -95, + -97, + -90, + -84, + -98, + -84, + -92, + -98, + -96, + -103, + -98, + -101, + -82, + -100, + -90, + -101, + -99, + -97, + -93, + -87, + -95, + -114, + -99, + -102, + -97, + -95, + -103, + -84, + -95, + -87, + -89, + -85, + -97, + -98, + -87, + -100, + -99, + -103, + -96, + -99, + -98, + -90, + -100, + -91, + -87, + -91, + -92, + -90, + -83, + -98, + -94, + -87, + -84, + -86, + -87, + -100, + -87, + -101, + -104, + -97, + -83, + -100, + -88, + -98, + -86, + -85, + -97, + -98, + -100, + -95, + -97, + -82, + -95, + -85, + -104, + -114, + -108, + -98, + -113, + -87, + -98, + -99, + -85, + -85, + -85, + -87, + -93, + -91, + -89, + -100, + -85, + -82, + -91, + -85, + -91, + -101, + -82, + -96, + -86, + -86, + -82, + -100, + -102, + -96, + -93, + -102, + -96, + -93, + -84, + -89, + -90, + -88, + -96, + -87, + -100, + -83, + -81, + -89, + -98, + -95, + -98, + -87, + -104, + -95, + -85, + -88, + -101, + -90, + -102, + -100, + -100, + -90, + -101, + -82, + -102, + -101, + -87, + -86, + -85, + -84, + -87, + -88, + -93, + -89, + -99, + -84, + -101, + -95, + -102, + -82, + -82, + -88, + -104, + -85, + -85, + -98, + -90, + -87, + -96, + -102, + -94, + -82, + -87, + -101, + -102, + -87, + -102, + -85, + -84, + -90, + -96, + -88, + -95, + -101, + -83, + -85, + -104, + -112, + -97, + -96, + -114, + -96, + -100, + -103, + -85, + -82, + -86, + -96, + -96, + -84, + -84, + -87, + -100, + -85, + -98, + -89, + -100, + -88, + -98, + -97, + -91, + -102, + -89, + -93, + -95, + -101, + -93, + -88, + -90, + -96, + -87, + -85, + -85, + -100, + -85, + -97, + -88, + -99, + -92, + -84, + -101, + -99, + -113, + -85, + -93, + -112, + -97, + -91, + -101, + -96, + -117, + -97, + -87, + -100, + -101, + -98, + -84, + -92, + -94, + -85, + -83, + -96, + -82, + -91, + -99, + -89, + -81, + -112, + -98, + -96, + -81, + -97, + -87, + -84, + -93, + -100, + -92, + -89, + -101, + -99, + -112, + -82, + -84, + -88, + -81, + -97, + -101, + -98, + -89, + -90, + -87, + -92, + -91, + -98, + -102, + -100, + -96, + -97, + -81, + -84, + -101, + -89, + -100, + -95, + -102, + -113, + -83, + -82, + -89, + -97, + -99, + -87, + -86, + -91, + -82, + -83, + -102, + -102, + -89, + -95, + -87, + -89, + -90, + -98, + -85, + -82, + -103, + -94, + -86, + -96, + -83, + -97, + -93, + -102, + -91, + -89, + -84, + -98, + -103, + -106, + -83, + -85, + -88, + -91, + -112, + -102, + -84, + -101, + -112, + -104, + -102, + -87, + -93, + -99, + -101, + -84, + -98, + -85, + -101, + -99, + -87, + -102, + -83, + -99, + -103, + -101, + -83, + -90, + -89, + -87, + -86, + -101, + -100, + -84, + -85, + -82, + -87, + -86, + -85, + -98, + -84, + -83, + -90, + -93, + -104, + -96, + -103, + -88, + -91, + -117, + -104, + -82, + -100, + -102, + -93, + -99, + -87, + -84, + -91, + -82, + -102, + -101, + -86, + -99, + -99, + -102, + -97, + -100, + -87, + -83, + -99, + -84, + -96, + -96, + -99, + -96, + -94, + -89, + -100, + -99, + -86, + -99, + -100, + -100, + -100, + -101, + -88, + -103, + -101, + -100, + -91, + -94, + -92, + -115, + -83, + -85, + -83, + -86, + -86, + -83, + -101, + -85, + -98, + -99, + -89, + -86, + -85, + -101, + -97, + -102, + -86, + -97, + -93, + -82, + -88, + -104, + -95, + -102, + -86, + -85, + -86, + -96, + -98, + -105, + -97, + -99, + -103, + -99, + -82, + -97, + -98, + -87, + -93, + -102, + -84, + -85, + -97, + -87, + -92, + -97, + -83, + -99, + -100, + -113, + -85, + -100, + -89, + -84, + -104, + -89, + -103, + -88, + -89, + -97, + -103, + -97, + -93, + -97, + -104, + -101, + -95, + -83, + -83, + -100, + -88, + -83, + -87, + -113, + -90, + -84, + -85, + -86, + -81, + -84, + -85, + -82, + -89, + -88, + -97, + -93, + -98, + -114, + -112, + -99, + -103, + -90, + -94, + -86, + -91, + -101, + -89, + -86, + -100, + -87, + -100, + -82, + -96, + -91, + -102, + -85, + -87, + -83, + -100, + -84, + -89, + -101, + -83, + -104, + -85, + -94, + -96, + -91, + -100, + -83, + -97, + -81, + -86, + -91, + -102, + -87, + -92, + -87, + -113, + -86, + -99, + -83, + -85, + -93, + -88, + -87, + -101, + -90, + -96, + -101, + -97, + -86, + -102, + -86, + -101, + -102, + -97, + -99, + -99, + -112, + -93, + -92, + -84, + -93, + -93, + -83, + -84, + -87, + -97, + -84, + -97, + -98, + -91, + -98, + -85, + -85, + -115, + -114, + -83, + -97, + -99, + -96, + -84, + -83, + -89, + -99, + -94, + -83, + -87, + -99, + -88, + -83, + -96, + -107, + -93, + -100, + -94, + -87, + -115, + -83, + -86, + -95, + -96, + -114, + -90, + -85, + -104, + -99, + -100, + -98, + -96, + -90, + -95, + -93, + -88, + -97, + -82, + -85, + -96, + -83, + -102, + -83, + -81, + -99, + -84, + -100, + -95, + -102, + -83, + -89, + -99, + -82, + -87, + -100, + -89, + -95, + -97, + -90, + -87, + -97, + -104, + -99, + -93, + -83, + -92, + -90, + -103, + -92, + -104, + -84, + -100, + -83, + -83, + -101, + -85, + -98, + -86, + -90, + -87, + -115, + -93, + -84, + -91, + -96, + -92, + -96, + -87, + -99, + -86, + -102, + -95, + -97, + -92, + -106, + -83, + -91, + -83, + -93, + -81, + -98, + -101, + -99, + -89, + -86, + -87, + -84, + -105, + -101, + -101, + -98, + -88, + -95, + -102, + -81, + -103, + -89, + -82, + -84, + -83, + -92, + -97, + -102, + -93, + -114, + -99, + -95, + -95, + -85, + -114, + -102, + -99, + -101, + -102, + -103, + -98, + -98, + -96, + -101, + -96, + -98, + -83, + -84, + -100, + -95, + -113, + -103, + -97, + -99, + -98, + -84, + -87, + -99, + -101, + -99, + -80, + -82, + -83, + -111, + -83, + -94, + -86, + -99, + -99, + -99, + -82, + -99, + -88, + -100, + -84, + -86, + -83, + -95, + -103, + -95, + -83, + -95, + -98, + -85, + -101, + -89, + -82, + -100, + -103, + -87, + -101, + -85, + -88, + -91, + -99, + -97, + -92, + -116, + -90, + -105, + -102, + -96, + -98, + -113, + -87, + -99, + -84, + -84, + -86, + -97, + -97, + -100, + -98, + -83, + -88, + -88, + -116, + -100, + -93, + -97, + -93, + -85, + -95, + -102, + -84, + -85, + -101, + -99, + -98, + -99, + -102, + -86, + -89, + -83, + -84, + -83, + -98, + -100, + -82, + -92, + -83, + -101, + -83, + -85, + -91, + -85, + -94, + -114, + -82, + -114, + -85, + -91, + -101, + -113, + -82, + -86, + -84, + -87, + -98, + -99, + -93, + -99, + -89, + -100, + -86, + -81, + -85, + -113, + -95, + -97, + -85, + -90, + -95, + -100, + -97, + -91, + -102, + -93, + -85, + -99, + -89, + -102, + -96, + -89, + -98, + -101, + -93, + -99, + -94, + -83, + -97, + -103, + -102, + -101, + -87, + -89, + -100, + -99, + -86, + -104, + -95, + -88, + -102, + -103, + -119, + -99, + -87, + -119, + -89, + -90, + -83, + -88, + -95, + -90, + -95, + -93, + -93, + -90, + -95, + -87, + -84, + -92, + -92, + -98, + -87, + -102, + -84, + -100, + -104, + -101, + -94, + -89, + -113, + -89, + -98, + -85, + -99, + -94, + -84, + -85, + -84, + -101, + -84, + -105, + -90, + -91, + -101, + -82, + -94, + -87, + -90, + -86, + -98, + -88, + -97, + -98, + -98, + -96, + -84, + -96, + -99, + -99, + -101, + -102, + -83, + -102, + -100, + -101, + -100, + -97, + -102, + -99, + -99, + -85, + -97, + -84, + -96, + -84, + -89, + -99, + -100, + -104, + -105, + -99, + -99, + -100, + -101, + -87, + -92, + -97, + -112, + -84, + -97, + -83, + -96, + -82, + -102, + -100, + -99, + -99, + -86, + -82, + -102, + -82, + -89, + -85, + -100, + -98, + -99, + -92, + -99, + -89, + -86, + -100, + -82, + -100, + -88, + -103, + -84, + -86, + -86, + -88, + -90, + -96, + -85, + -101, + -85, + -99, + -86, + -89, + -82, + -94, + -88, + -88, + -113, + -85, + -84, + -91, + -90, + -84, + -113, + -98, + -86, + -102, + -106, + -101, + -84, + -96, + -84, + -85, + -86, + -119, + -100, + -86, + -99, + -83, + -103, + -96, + -100, + -96, + -104, + -81, + -99, + -87, + -89, + -102, + -84, + -100, + -101, + -98, + -95, + -117, + -114, + -84, + -83, + -93, + -95, + -116, + -95, + -91, + -95, + -85, + -88, + -92, + -85, + -100, + -84, + -99, + -87, + -88, + -98, + -106, + -102, + -98, + -98, + -102, + -98, + -82, + -93, + -101, + -86, + -86, + -94, + -84, + -118, + -102, + -98, + -97, + -104, + -95, + -100, + -99, + -91, + -92, + -99, + -84, + -84, + -111, + -100, + -97, + -83, + -85, + -103, + -100, + -95, + -101, + -99, + -101, + -99, + -85, + -88, + -102, + -98, + -100, + -101, + -99, + -103, + -92, + -84, + -82, + -92, + -97, + -97, + -90, + -103, + -99, + -82, + -100, + -99, + -94, + -96, + -103, + -87, + -95, + -113, + -83, + -97, + -90, + -97, + -99, + -103, + -101, + -97, + -101, + -94, + -94, + -90, + -85, + -88, + -86, + -84, + -85, + -83, + -100, + -84, + -86, + -115, + -83, + -87, + -84, + -83, + -102, + -82, + -102, + -91, + -88, + -92, + -85, + -102, + -99, + -99, + -100, + -103, + -82, + -98, + -86, + -99, + -99, + -92, + -100, + -95, + -92, + -86, + -88, + -85, + -91, + -84, + -103, + -91, + -100, + -97, + -100, + -86, + -84, + -102, + -88, + -100, + -87, + -86, + -86, + -89, + -93, + -85, + -96, + -114, + -102, + -113, + -101, + -88, + -91, + -101, + -99, + -86, + -84, + -89, + -89, + -98, + -101, + -103, + -90, + -85, + -100, + -82, + -85, + -91, + -85, + -114, + -97, + -100, + -84, + -96, + -86, + -91, + -94, + -84, + -85, + -85, + -98, + -82, + -94, + -84, + -84, + -103, + -92, + -84, + -99, + -97, + -89, + -84, + -89, + -85, + -98, + -92, + -90, + -87, + -99, + -97, + -84, + -83, + -97, + -104, + -84, + -90, + -89, + -87, + -84, + -95, + -97, + -85, + -83, + -86, + -103, + -99, + -96, + -91, + -83, + -99, + -84, + -87, + -94, + -96, + -92, + -82, + -84, + -84, + -101, + -96, + -98, + -99, + -88, + -101, + -86, + -96, + -100, + -84, + -91, + -102, + -100, + -88, + -99, + -82, + -95, + -86, + -99, + -89, + -96, + -114, + -97, + -97, + -90, + -97, + -87, + -85, + -85, + -85, + -95, + -93, + -81, + -89, + -85, + -100, + -101, + -100, + -100, + -91, + -99, + -83, + -96, + -99, + -90, + -97, + -97, + -85, + -85, + -93, + -85, + -100, + -84, + -101, + -88, + -97, + -99, + -99, + -99, + -99, + -101, + -98, + -100, + -89, + -103, + -101, + -102, + -87, + -97, + -101, + -92, + -86, + -84, + -84, + -82, + -92, + -98, + -96, + -82, + -91, + -98, + -81, + -98, + -102, + -85, + -100, + -93, + -94, + -97, + -99, + -106, + -89, + -88, + -89, + -88, + -89, + -100, + -99, + -85, + -88, + -100, + -95, + -101, + -88, + -87, + -94, + -101, + -97, + -94, + -98, + -90, + -111, + -83, + -97, + -83, + -86, + -90, + -84, + -86, + -83, + -88, + -85, + -114, + -88, + -84, + -94, + -93, + -89, + -102, + -97, + -98, + -97, + -95, + -88, + -89, + -101, + -99, + -96, + -97, + -83, + -83, + -83, + -91, + -96, + -102, + -87, + -92, + -87, + -88, + -95, + -101, + -95, + -100, + -97, + -98, + -86, + -105, + -87, + -95, + -98, + -101, + -100, + -82, + -95, + -101, + -92, + -86, + -102, + -90, + -82, + -103, + -84, + -92, + -98, + -89, + -100, + -103, + -100, + -102, + -97, + -86, + -94, + -103, + -84, + -88, + -84, + -97, + -82, + -82, + -97, + -94, + -95, + -87, + -103, + -102, + -83, + -115, + -90, + -103, + -94, + -98, + -85, + -96, + -89, + -89, + -100, + -89, + -98, + -88, + -103, + -98, + -86, + -83, + -84, + -98, + -86, + -102, + -86, + -105, + -100, + -102, + -104, + -99, + -82, + -93, + -101, + -97, + -82, + -90, + -85, + -88, + -98, + -100, + -90, + -87, + -86, + -90, + -82, + -84, + -95, + -93, + -94, + -93, + -85, + -99, + -83, + -87, + -92, + -87, + -98, + -83, + -82, + -94, + -86, + -96, + -87, + -97, + -91, + -99, + -91, + -105, + -84, + -89, + -97, + -93, + -88, + -104, + -83, + -105, + -97, + -103, + -92, + -82, + -85, + -98, + -90, + -86, + -88, + -99, + -105, + -115, + -83, + -87, + -85, + -101, + -93, + -85, + -89, + -82, + -100, + -84, + -86, + -85, + -89, + -83, + -99, + -103, + -96, + -101, + -92, + -92, + -97, + -99, + -102, + -89, + -92, + -100, + -96, + -104, + -97, + -101, + -97, + -84, + -101, + -93, + -100, + -90, + -84, + -83, + -85, + -102, + -84, + -101, + -82, + -86, + -104, + -104, + -91, + -89, + -84, + -93, + -98, + -87, + -100, + -116, + -84, + -85, + -89, + -91, + -98, + -96, + -89, + -90, + -87, + -103, + -87, + -95, + -100, + -95, + -101, + -100, + -92, + -95, + -103, + -87, + -100, + -84, + -100, + -80, + -104, + -89, + -96, + -87, + -100, + -98, + -99, + -96, + -84, + -101, + -113, + -100, + -100, + -100, + -96, + -94, + -101, + -85, + -85, + -96, + -89, + -91, + -86, + -83, + -92, + -98, + -94, + -89, + -86, + -117, + -85, + -117, + -99, + -86, + -99, + -100, + -101, + -87, + -95, + -98, + -105, + -102, + -106, + -84, + -96, + -99, + -87, + -87, + -87, + -85, + -90, + -100, + -100, + -99, + -89, + -97, + -103, + -97, + -87, + -85, + -99, + -99, + -89, + -91, + -99, + -94, + -84, + -85, + -92, + -91, + -97, + -90, + -87, + -98, + -100, + -83, + -101, + -97, + -86, + -84, + -94, + -82, + -85, + -104, + -96, + -93, + -98, + -104, + -97, + -98, + -83, + -86, + -101, + -85, + -98, + -89, + -91, + -84, + -94, + -89, + -84, + -84, + -99, + -83, + -82, + -84, + -90, + -99, + -97, + -96, + -98, + -104, + -86, + -100, + -98, + -82, + -95, + -101, + -91, + -103, + -82, + -92, + -97, + -83, + -94, + -96, + -100, + -88, + -86, + -92, + -97, + -84, + -105, + -89, + -93, + -114, + -91, + -98, + -92, + -96, + -115, + -98, + -83, + -81, + -87, + -85, + -118, + -90, + -97, + -87, + -84, + -100, + -85, + -90, + -86, + -98, + -82, + -96, + -100, + -101, + -86, + -83, + -85, + -89, + -99, + -99, + -94, + -98, + -95, + -92, + -91, + -101, + -99, + -98, + -99, + -91, + -86, + -117, + -98, + -99, + -87, + -81, + -84, + -100, + -101, + -97, + -98, + -97, + -88, + -119, + -102, + -102, + -104, + -85, + -97, + -102, + -100, + -104, + -87, + -82, + -86, + -93, + -100, + -99, + -84, + -98, + -84, + -86, + -100, + -88, + -103, + -85, + -88, + -99, + -96, + -99, + -87, + -84, + -83, + -96, + -97, + -86, + -97, + -97, + -86, + -82, + -100, + -85, + -89, + -95, + -96, + -101, + -88, + -87, + -96, + -84, + -87, + -90, + -100, + -96, + -90, + -88, + -87, + -96, + -102, + -92, + -101, + -92, + -84, + -85, + -86, + -116, + -103, + -84, + -98, + -93, + -98, + -97, + -99, + -86, + -84, + -85, + -87, + -98, + -99, + -101, + -97, + -96, + -85, + -80, + -115, + -81, + -83, + -95, + -100, + -83, + -96, + -89, + -99, + -90, + -84, + -101, + -96, + -84, + -100, + -84, + -97, + -97, + -82, + -99, + -98, + -85, + -98, + -93, + -89, + -105, + -100, + -94, + -83, + -92, + -98, + -87, + -116, + -87, + -92, + -91, + -85, + -86, + -85, + -103, + -93, + -112, + -89, + -88, + -90, + -88, + -114, + -84, + -92, + -86, + -98, + -104, + -84, + -90, + -90, + -93, + -84, + -89, + -103, + -98, + -87, + -98, + -92, + -88, + -98, + -96, + -91, + -86, + -96, + -102, + -91, + -86, + -103, + -99, + -97, + -83, + -97, + -113, + -82, + -84, + -103, + -101, + -83, + -101, + -100, + -97, + -94, + -89, + -100, + -87, + -102, + -89, + -86, + -86, + -93, + -96, + -98, + -101, + -98, + -98, + -97, + -97, + -100, + -91, + -100, + -99, + -114, + -84, + -104, + -95, + -84, + -91, + -103, + -82, + -90, + -90, + -89, + -98, + -101, + -91, + -84, + -99, + -99, + -84, + -86, + -83, + -95, + -96, + -91, + -84, + -99, + -104, + -88, + -98, + -83, + -103, + -91, + -92, + -95, + -98, + -94, + -104, + -82, + -87, + -99, + -85, + -88, + -95, + -91, + -97, + -103, + -92, + -85, + -86, + -100, + -86, + -99, + -89, + -97, + -86, + -99, + -80, + -88, + -102, + -97, + -101, + -95, + -97, + -87, + -102, + -87, + -90, + -83, + -104, + -101, + -105, + -85, + -99, + -82, + -84, + -99, + -85, + -96, + -98, + -100, + -99, + -99, + -89, + -87, + -86, + -100, + -85, + -87, + -99, + -93, + -113, + -96, + -84, + -93, + -94, + -90, + -94, + -91, + -116, + -98, + -85, + -84, + -85, + -84, + -100, + -93, + -97, + -114, + -94, + -102, + -89, + -87, + -96, + -87, + -100, + -98, + -86, + -92, + -93, + -96, + -99, + -89, + -82, + -98, + -99, + -99, + -93, + -100, + -88, + -85, + -92, + -85, + -87, + -89, + -92, + -101, + -113, + -100, + -96, + -87, + -97, + -95, + -100, + -84, + -85, + -98, + -97, + -100, + -104, + -89, + -100, + -99, + -100, + -93, + -84, + -118, + -99, + -97, + -104, + -91, + -98, + -92, + -113, + -101, + -89, + -98, + -100, + -98, + -86, + -95, + -86, + -100, + -86, + -96, + -100, + -84, + -101, + -100, + -83, + -95, + -84, + -101, + -102, + -99, + -103, + -96, + -81, + -98, + -84, + -115, + -101, + -84, + -99, + -93, + -98, + -101, + -85, + -84, + -83, + -104, + -82, + -104, + -100, + -84, + -79, + -89, + -82, + -84, + -99, + -83, + -96, + -86, + -84, + -93, + -84, + -88, + -101, + -93, + -86, + -95, + -102, + -100, + -119, + -87, + -98, + -93, + -93, + -100, + -81, + -83, + -90, + -101, + -101, + -86, + -99, + -94, + -88, + -102, + -102, + -88, + -83, + -84, + -87, + -101, + -116, + -91, + -89, + -88, + -102, + -99, + -101, + -101, + -86, + -100, + -87, + -101, + -99, + -85, + -101, + -98, + -96, + -85, + -96, + -90, + -100, + -89, + -93, + -96, + -83, + -100, + -99, + -102, + -86, + -86, + -95, + -94, + -105, + -102, + -93, + -85, + -94, + -83, + -94, + -87, + -101, + -100, + -83, + -100, + -94, + -86, + -87, + -91, + -95, + -100, + -93, + -98, + -96, + -98, + -94, + -85, + -91, + -102, + -90, + -91, + -97, + -113, + -103, + -85, + -87, + -104, + -102, + -93, + -87, + -92, + -102, + -84, + -99, + -102, + -88, + -85, + -101, + -91, + -100, + -87, + -98, + -81, + -100, + -86, + -98, + -102, + -85, + -102, + -81, + -98, + -94, + -104, + -87, + -112, + -99, + -89, + -84, + -99, + -106, + -99, + -87, + -100, + -85, + -98, + -83, + -90, + -100, + -95, + -86, + -104, + -89, + -102, + -95, + -87, + -87, + -98, + -83, + -87, + -83, + -102, + -91, + -90, + -112, + -100, + -84, + -99, + -98, + -81, + -90, + -98, + -101, + -101, + -99, + -86, + -102, + -119, + -85, + -102, + -87, + -91, + -83, + -95, + -98, + -91, + -101, + -100, + -92, + -89, + -102, + -92, + -99, + -85, + -95, + -102, + -85, + -97, + -114, + -82, + -99, + -98, + -93, + -87, + -90, + -91, + -86, + -99, + -83, + -103, + -90, + -87, + -115, + -100, + -96, + -113, + -91, + -104, + -87, + -83, + -106, + -100, + -102, + -93, + -96, + -84, + -90, + -97, + -87, + -99, + -88, + -99, + -88, + -90, + -84, + -83, + -93, + -95, + -95, + -83, + -104, + -102, + -86, + -84, + -100, + -99, + -84, + -85, + -99, + -81, + -95, + -101, + -91, + -96, + -97, + -99, + -112, + -99, + -82, + -87, + -100, + -85, + -100, + -94, + -91, + -98, + -81, + -90, + -86, + -85, + -119, + -99, + -101, + -84, + -86, + -83, + -85, + -97, + -99, + -101, + -88, + -95, + -89, + -96, + -98, + -97, + -116, + -102, + -87, + -98, + -91, + -90, + -96, + -98, + -83, + -90, + -103, + -85, + -98, + -115, + -91, + -99, + -101, + -117, + -87, + -96, + -100, + -85, + -94, + -91, + -86, + -100, + -101, + -95, + -84, + -82, + -83, + -91, + -93, + -99, + -96, + -97, + -83, + -84, + -89, + -85, + -103, + -95, + -103, + -98, + -100, + -85, + -96, + -102, + -91, + -96, + -94, + -84, + -89, + -95, + -102, + -102, + -84, + -83, + -102, + -95, + -100, + -92, + -95, + -90, + -92, + -86, + -84, + -98, + -83, + -91, + -98, + -83, + -92, + -101, + -99, + -97, + -99, + -99, + -88, + -101, + -84, + -97, + -99, + -90, + -98, + -100, + -93, + -84, + -96, + -105, + -99, + -90, + -114, + -94, + -101, + -93, + -98, + -96, + -101, + -84, + -98, + -101, + -98, + -119, + -116, + -86, + -84, + -119, + -98, + -99, + -97, + -115, + -97, + -86, + -89, + -100, + -114, + -102, + -98, + -89, + -87, + -99, + -89, + -89, + -96, + -93, + -88, + -114, + -86, + -89, + -103, + -102, + -100, + -90, + -93, + -84, + -90, + -83, + -96, + -99, + -96, + -84, + -96, + -89, + -93, + -87, + -99, + -98, + -86, + -83, + -103, + -99, + -91, + -88, + -85, + -84, + -114, + -101, + -83, + -97, + -100, + -93, + -87, + -98, + -90, + -95, + -101, + -86, + -87, + -90, + -97, + -83, + -89, + -87, + -101, + -91, + -91, + -93, + -99, + -83, + -94, + -85, + -82, + -96, + -96, + -85, + -90, + -99, + -98, + -84, + -89, + -98, + -85, + -90, + -99, + -112, + -90, + -102, + -86, + -93, + -94, + -93, + -86, + -104, + -82, + -102, + -94, + -99, + -103, + -83, + -86, + -85, + -86, + -85, + -98, + -97, + -98, + -95, + -93, + -91, + -84, + -84, + -93, + -90, + -98, + -99, + -97, + -91, + -99, + -103, + -86, + -82, + -100, + -100, + -83, + -84, + -118, + -84, + -97, + -100, + -117, + -91, + -86, + -100, + -102, + -88, + -89, + -89, + -102, + -102, + -91, + -98, + -99, + -95, + -92, + -100, + -92, + -91, + -84, + -96, + -98, + -100, + -102, + -101, + -86, + -93, + -85, + -100, + -84, + -98, + -98, + -100, + -85, + -89, + -99, + -91, + -116, + -106, + -98, + -84, + -102, + -98, + -94, + -95, + -100, + -99, + -99, + -83, + -84, + -88, + -91, + -80, + -85, + -87, + -119, + -99, + -104, + -93, + -100, + -94, + -99, + -96, + -81, + -102, + -93, + -99, + -119, + -100, + -102, + -98, + -101, + -102, + -99, + -112, + -118, + -91, + -100, + -87, + -88, + -100, + -97, + -96, + -101, + -84, + -87, + -101, + -91, + -96, + -87, + -101, + -93, + -96, + -88, + -99, + -97, + -114, + -98, + -83, + -85, + -91, + -90, + -87, + -86, + -86, + -82, + -85, + -86, + -84, + -97, + -87, + -95, + -103, + -86, + -100, + -99, + -100, + -81, + -94, + -100, + -101, + -82, + -90, + -83, + -118, + -102, + -98, + -84, + -96, + -85, + -111, + -99, + -87, + -101, + -101, + -99, + -98, + -93, + -101, + -101, + -88, + -95, + -84, + -97, + -102, + -97, + -89, + -98, + -97, + -101, + -95, + -100, + -85, + -84, + -96, + -95, + -84, + -92, + -103, + -119, + -87, + -90, + -95, + -84, + -84, + -83, + -99, + -102, + -91, + -88, + -100, + -95, + -99, + -102, + -88, + -86, + -92, + -91, + -84, + -88, + -86, + -98, + -83, + -87, + -97, + -96, + -104, + -94, + -98, + -85, + -113, + -86, + -98, + -100, + -91, + -88, + -100, + -95, + -88, + -85, + -91, + -88, + -88, + -88, + -100, + -84, + -101, + -86, + -103, + -93, + -89, + -98, + -84, + -104, + -115, + -81, + -88, + -97, + -88, + -89, + -80, + -96, + -98, + -99, + -102, + -102, + -83, + -93, + -98, + -84, + -102, + -113, + -84, + -86, + -84, + -102, + -84, + -112, + -104, + -81, + -98, + -96, + -100, + -90, + -85, + -98, + -98, + -82, + -101, + -97, + -98, + -89, + -93, + -97, + -94, + -90, + -85, + -97, + -103, + -84, + -89, + -97, + -94, + -100, + -89, + -99, + -95, + -86, + -92, + -86, + -89, + -89, + -114, + -93, + -101, + -102, + -102, + -96, + -88, + -88, + -84, + -99, + -99, + -119, + -95, + -101, + -84, + -90, + -87, + -92, + -104, + -88, + -83, + -90, + -96, + -85, + -113, + -98, + -93, + -97, + -101, + -94, + -95, + -103, + -101, + -100, + -100, + -103, + -86, + -85, + -84, + -95, + -85, + -97, + -86, + -84, + -87, + -100, + -90, + -114, + -84, + -84, + -96, + -100, + -95, + -90, + -88, + -91, + -101, + -100, + -99, + -95, + -101, + -119, + -117, + -85, + -118, + -100, + -116, + -91, + -84, + -92, + -99, + -90, + -99, + -103, + -85, + -97, + -98, + -99, + -89, + -102, + -99, + -89, + -89, + -99, + -85, + -87, + -98, + -103, + -98, + -93, + -98, + -93, + -89, + -90, + -95, + -95, + -86, + -91, + -102, + -86, + -101, + -84, + -83, + -87, + -99, + -88, + -86, + -99, + -95, + -101, + -94, + -96, + -93, + -99, + -117, + -90, + -103, + -103, + -89, + -97, + -98, + -86, + -97, + -84, + -84, + -82, + -85, + -89, + -82, + -100, + -101, + -102, + -83, + -94, + -93, + -83, + -90, + -85, + -90, + -93, + -97, + -86, + -88, + -101, + -97, + -101, + -98, + -99, + -98, + -99, + -95, + -102, + -116, + -119, + -97, + -90, + -93, + -90, + -102, + -99, + -99, + -97, + -82, + -83, + -100, + -102, + -101, + -91, + -99, + -100, + -102, + -101, + -100, + -96, + -86, + -87, + -99, + -82, + -100, + -90, + -119, + -100, + -83, + -85, + -85, + -99, + -93, + -96, + -103, + -101, + -100, + -103, + -97, + -89, + -90, + -88, + -100, + -99, + -98, + -101, + -93, + -98, + -95, + -84, + -99, + -93, + -98, + -88, + -95, + -102, + -97, + -86, + -89, + -101, + -100, + -98, + -87, + -98, + -99, + -100, + -97, + -99, + -83, + -90, + -90, + -88, + -92, + -96, + -103, + -101, + -88, + -85, + -114, + -114, + -84, + -103, + -103, + -98, + -97, + -84, + -86, + -97, + -83, + -100, + -99, + -100, + -83, + -90, + -103, + -95, + -101, + -102, + -96, + -93, + -87, + -86, + -87, + -88, + -86, + -100, + -92, + -100, + -98, + -95, + -86, + -97, + -96, + -95, + -90, + -118, + -99, + -103, + -93, + -86, + -84, + -101, + -87, + -87, + -97, + -100, + -88, + -95, + -100, + -96, + -94, + -96, + -86, + -85, + -91, + -91, + -102, + -92, + -93, + -112, + -99, + -91, + -100, + -93, + -100, + -117, + -86, + -101, + -84, + -88, + -98, + -90, + -100, + -99, + -88, + -102, + -86, + -102, + -97, + -100, + -90, + -102, + -99, + -94, + -87, + -97, + -101, + -92, + -86, + -84, + -91, + -101, + -114, + -95, + -86, + -98, + -99, + -104, + -96, + -86, + -90, + -87, + -101, + -96, + -100, + -98, + -85, + -83, + -94, + -98, + -101, + -99, + -89, + -90, + -95, + -94, + -118, + -81, + -84, + -102, + -88, + -87, + -100, + -102, + -93, + -85, + -102, + -99, + -88, + -100, + -99, + -93, + -103, + -95, + -91, + -94, + -98, + -101, + -97, + -84, + -100, + -93, + -103, + -91, + -94, + -90, + -92, + -100, + -115, + -88, + -96, + -101, + -98, + -82, + -91, + -93, + -102, + -83, + -97, + -97, + -87, + -87, + -96, + -87, + -90, + -90, + -96, + -87, + -84, + -87, + -98, + -103, + -85, + -84, + -92, + -84, + -97, + -99, + -96, + -93, + -100, + -115, + -85, + -85, + -94, + -118, + -83, + -100, + -98, + -87, + -97, + -94, + -114, + -90, + -100, + -91, + -84, + -113, + -89, + -103, + -88, + -101, + -91, + -104, + -97, + -99, + -90, + -84, + -114, + -112, + -94, + -98, + -98, + -84, + -94, + -93, + -99, + -101, + -98, + -90, + -85, + -103, + -86, + -104, + -93, + -91, + -101, + -90, + -93, + -94, + -97, + -102, + -96, + -102, + -97, + -100, + -118, + -94, + -100, + -87, + -99, + -84, + -96, + -82, + -91, + -98, + -101, + -99, + -86, + -84, + -93, + -85, + -96, + -100, + -83, + -93, + -102, + -88, + -91, + -116, + -90, + -92, + -89, + -100, + -101, + -118, + -96, + -88, + -87, + -98, + -95, + -93, + -116, + -101, + -99, + -114, + -97, + -93, + -102, + -96, + -83, + -91, + -118, + -90, + -91, + -84, + -99, + -100, + -83, + -101, + -87, + -93, + -99, + -101, + -97, + -115, + -96, + -91, + -87, + -86, + -94, + -83, + -95, + -100, + -98, + -87, + -91, + -84, + -83, + -85, + -84, + -93, + -91, + -99, + -90, + -96, + -100, + -103, + -97, + -97, + -83, + -99, + -99, + -85, + -95, + -91, + -94, + -85, + -95, + -97, + -100, + -94, + -88, + -87, + -102, + -100, + -99, + -91, + -98, + -82, + -99, + -94, + -84, + -89, + -87, + -114, + -95, + -97, + -97, + -98, + -88, + -101, + -85, + -97, + -94, + -99, + -102, + -118, + -99, + -92, + -98, + -91, + -91, + -91, + -95, + -97, + -95, + -100, + -95, + -93, + -98, + -90, + -87, + -102, + -102, + -89, + -89, + -98, + -93, + -113, + -92, + -92, + -100, + -89, + -98, + -99, + -100, + -90, + -95, + -98, + -101, + -99, + -92, + -92, + -99, + -84, + -102, + -91, + -113, + -86, + -98, + -103, + -102, + -119, + -85, + -101, + -93, + -99, + -103, + -82, + -103, + -97, + -94, + -99, + -102, + -101, + -87, + -96, + -101, + -99, + -99, + -102, + -116, + -98, + -100, + -85, + -93, + -99, + -98, + -87, + -101, + -84, + -113, + -83, + -98, + -92, + -96, + -114, + -102, + -89, + -91, + -102, + -101, + -98, + -100, + -88, + -100, + -97, + -87, + -103, + -98, + -101, + -85, + -93, + -99, + -97, + -91, + -98, + -86, + -101, + -117, + -84, + -95, + -101, + -83, + -100, + -100, + -117, + -99, + -83, + -91, + -102, + -83, + -101, + -95, + -98, + -101, + -94, + -85, + -94, + -114, + -117, + -86, + -98, + -100, + -91, + -91, + -84, + -103, + -102, + -88, + -114, + -103, + -98, + -87, + -117, + -101, + -84, + -98, + -94, + -98, + -95, + -98, + -99, + -96, + -97, + -89, + -90, + -100, + -100, + -99, + -86, + -100, + -114, + -100, + -103, + -87, + -90, + -86, + -93, + -94, + -90, + -101, + -99, + -85, + -87, + -83, + -89, + -86, + -84, + -86, + -97, + -88, + -84, + -96, + -86, + -83, + -104, + -87, + -99, + -101, + -96, + -102, + -86, + -101, + -83, + -105, + -92, + -95, + -82, + -98, + -84, + -88, + -97, + -98, + -99, + -85, + -93, + -98, + -96, + -100, + -102, + -96, + -92, + -118, + -100, + -97, + -98, + -119, + -86, + -93, + -99, + -90, + -103, + -100, + -99, + -86, + -84, + -101, + -99, + -92, + -118, + -92, + -101, + -95, + -101, + -104, + -91, + -91, + -100, + -86, + -116, + -114, + -98, + -102, + -94, + -103, + -100, + -98, + -97, + -97, + -98, + -93, + -119, + -99, + -119, + -86, + -101, + -90, + -104, + -96, + -81, + -88, + -88, + -94, + -118, + -97, + -102, + -100, + -92, + -100, + -100, + -99, + -99, + -90, + -96, + -99, + -95, + -102, + -101, + -101, + -91, + -85, + -97, + -101, + -96, + -85, + -100, + -85, + -99, + -99, + -96, + -105, + -96, + -91, + -87, + -97, + -98, + -97, + -97, + -100, + -87, + -89, + -96, + -95, + -85, + -96, + -101, + -100, + -97, + -96, + -86, + -97, + -116, + -99, + -103, + -100, + -98, + -101, + -99, + -99, + -101, + -101, + -99, + -113, + -102, + -99, + -87, + -100, + -100, + -96, + -88, + -99, + -89, + -103, + -93, + -87, + -91, + -101, + -90, + -94, + -89, + -102, + -100, + -96, + -92, + -97, + -96, + -112, + -88, + -94, + -97, + -103, + -100, + -96, + -95, + -101, + -98, + -101, + -88, + -88, + -98, + -87, + -86, + -99, + -84, + -90, + -97, + -88, + -102, + -99, + -85, + -98, + -118, + -102, + -84, + -104, + -89, + -84, + -86, + -102, + -89, + -84, + -97, + -98, + -89, + -91, + -88, + -85, + -88, + -87, + -87, + -92, + -87, + -100, + -96, + -100, + -95, + -88, + -99, + -113, + -101, + -92, + -83, + -103, + -101, + -98, + -99, + -101, + -88, + -93, + -90, + -85, + -95, + -97, + -90, + -90, + -101, + -101, + -89, + -86, + -100, + -98, + -94, + -91, + -100, + -86, + -88, + -96, + -97, + -100, + -95, + -84, + -96, + -90, + -118, + -94, + -102, + -100, + -101, + -97, + -103, + -89, + -102, + -91, + -88, + -88, + -86, + -85, + -88, + -99, + -99, + -83, + -85, + -85, + -91, + -98, + -84, + -104, + -84, + -116, + -99, + -100, + -97, + -101, + -98, + -100, + -90, + -98, + -117, + -86, + -99, + -97, + -100, + -84, + -102, + -87, + -103, + -85, + -87, + -93, + -93, + -90, + -100, + -85, + -93, + -102, + -117, + -84, + -85, + -101, + -92, + -99, + -104, + -98, + -95, + -85, + -101, + -101, + -100, + -98, + -91, + -102, + -99, + -98, + -98, + -90, + -96, + -96, + -85, + -97, + -101, + -88, + -85, + -100, + -98, + -93, + -99, + -116, + -98, + -97, + -98, + -100, + -94, + -86, + -93, + -97, + -119, + -99, + -84, + -95, + -95, + -118, + -101, + -99, + -98, + -96, + -94, + -93, + -95, + -89, + -101, + -85, + -98, + -102, + -87, + -95, + -94, + -94, + -100, + -89, + -93, + -88, + -97, + -102, + -104, + -88, + -99, + -118, + -86, + -100, + -94, + -97, + -82, + -99, + -91, + -97, + -116, + -98, + -84, + -99, + -99, + -91, + -83, + -94, + -96, + -99, + -82, + -95, + -83, + -94, + -102, + -100, + -101, + -100, + -97, + -84, + -101, + -93, + -96, + -92, + -92, + -99, + -99, + -118, + -98, + -94, + -98, + -101, + -84, + -88, + -118, + -103, + -101, + -88, + -98, + -98, + -117, + -100, + -104, + -87, + -82, + -99, + -101, + -93, + -89, + -101, + -84, + -100, + -99, + -87, + -100, + -101, + -95, + -96, + -97, + -118, + -84, + -94, + -93, + -117, + -117, + -98, + -100, + -97, + -85, + -91, + -99, + -97, + -93, + -90, + -100, + -97, + -85, + -87, + -96, + -98, + -117, + -97, + -117, + -95, + -98, + -93, + -117, + -88, + -83, + -86, + -81, + -87, + -102, + -99, + -89, + -117, + -103, + -88, + -93, + -100, + -96, + -99, + -98, + -85, + -94, + -92, + -98, + -97, + -99, + -92, + -98, + -94, + -91, + -118, + -118, + -99, + -87, + -89, + -99, + -118, + -88, + -84, + -98, + -99, + -94, + -85, + -118, + -102, + -87, + -93, + -84, + -100, + -96, + -90, + -89, + -97, + -87, + -92, + -94, + -99, + -87, + -94, + -117, + -102, + -118, + -94, + -102, + -97, + -85, + -118, + -91, + -97, + -89, + -115, + -100, + -102, + -91, + -101, + -86, + -85, + -98, + -95, + -89, + -113, + -98, + -97, + -99, + -102, + -93, + -101, + -97, + -94, + -96, + -86, + -117, + -100, + -102, + -90, + -117, + -82, + -91, + -82, + -96, + -90, + -102, + -93, + -97, + -89, + -84, + -91, + -85, + -117, + -99, + -91, + -98, + -105, + -103, + -101, + -101, + -90, + -95, + -85, + -101, + -114, + -98, + -98, + -116, + -112, + -100, + -99, + -86, + -93, + -99, + -102, + -84, + -102, + -92, + -96, + -99, + -89, + -91, + -101, + -99, + -94, + -92, + -96, + -96, + -97, + -86, + -94, + -102, + -96, + -95, + -101, + -89, + -86, + -99, + -100, + -93, + -117, + -102, + -95, + -91, + -98, + -117, + -116, + -102, + -98, + -99, + -87, + -100, + -88, + -99, + -84, + -93, + -95, + -100, + -94, + -96, + -102, + -97, + -92, + -87, + -99, + -100, + -98, + -92, + -100, + -94, + -100, + -87, + -97, + -99, + -105, + -101, + -93, + -88, + -98, + -99, + -118, + -98, + -102, + -92, + -96, + -95, + -103, + -89, + -98, + -91, + -100, + -96, + -117, + -86, + -101, + -98, + -92, + -92, + -89, + -94, + -117, + -100, + -117, + -92, + -90, + -84, + -100, + -98, + -86, + -96, + -96, + -84, + -100, + -102, + -89, + -94, + -95, + -87, + -101, + -102, + -88, + -95, + -100, + -100, + -101, + -84, + -98, + -95, + -98, + -99, + -98, + -86, + -93, + -98, + -101, + -101, + -97, + -100, + -97, + -101, + -84, + -116, + -100, + -95, + -87, + -97, + -98, + -99, + -99, + -95, + -92, + -83, + -92, + -99, + -91, + -99, + -85, + -99, + -98, + -94, + -113, + -100, + -100, + -101, + -99, + -98, + -84, + -92, + -117, + -99, + -94, + -90, + -83, + -99, + -102, + -86, + -85, + -96, + -88, + -96, + -100, + -101, + -100, + -98, + -103, + -100, + -99, + -97, + -86, + -101, + -95, + -101, + -97, + -99, + -118, + -97, + -100, + -95, + -87, + -97, + -118, + -99, + -92, + -100, + -97, + -89, + -88, + -100, + -112, + -82, + -94, + -97, + -117, + -88, + -104, + -87, + -99, + -99, + -99, + -98, + -98, + -99, + -96, + -90, + -102, + -92, + -102, + -97, + -102, + -86, + -85, + -102, + -96, + -98, + -117, + -85, + -98, + -117, + -97, + -113, + -93, + -101, + -100, + -117, + -93, + -95, + -99, + -118, + -88, + -98, + -96, + -119, + -90, + -91, + -92, + -97, + -98, + -95, + -99, + -99, + -85, + -97, + -104, + -91, + -91, + -99, + -85, + -97, + -92, + -101, + -100, + -85, + -117, + -100, + -98, + -99, + -96, + -117, + -94, + -98, + -91, + -98, + -102, + -114, + -98, + -97, + -91, + -98, + -83, + -95, + -105, + -96, + -98, + -118, + -101, + -97, + -95, + -97, + -116, + -99, + -99, + -102, + -85, + -95, + -117, + -97, + -99, + -93, + -97, + -101, + -90, + -95, + -97, + -98, + -88, + -94, + -101, + -84, + -86, + -117, + -100, + -100, + -98, + -117, + -98, + -90, + -101, + -99, + -116, + -100, + -87, + -102, + -103, + -98, + -90, + -92, + -95, + -95, + -100, + -118, + -97, + -118, + -96, + -97, + -103, + -100, + -84, + -85, + -99, + -98, + -100, + -99, + -116, + -118, + -98, + -102, + -92, + -98, + -100, + -83, + -86, + -94, + -99, + -98, + -104, + -97, + -118, + -117, + -97, + -88, + -100, + -118, + -102, + -83, + -92, + -93, + -93, + -100, + -101, + -97, + -116, + -101, + -117, + -96, + -94, + -94, + -100, + -102, + -97, + -97, + -95, + -97, + -98, + -100, + -103, + -100, + -91, + -117, + -96, + -85, + -90, + -89, + -87, + -89, + -102, + -117, + -118, + -99, + -99, + -103, + -117, + -101, + -86, + -97, + -95, + -100, + -95, + -98, + -98, + -101, + -83, + -95, + -95, + -97, + -102, + -93, + -95, + -86, + -98, + -96, + -90, + -97, + -89, + -102, + -94, + -93, + -84, + -100, + -102, + -86, + -101, + -92, + -102, + -91, + -102, + -103, + -103, + -97, + -114, + -90, + -100, + -87, + -117, + -98, + -98, + -88, + -103, + -91, + -98, + -100, + -88, + -99, + -117, + -96, + -87, + -99, + -88, + -102, + -101, + -96, + -96, + -117, + -100, + -97, + -90, + -98, + -99, + -101, + -94, + -84, + -99, + -96, + -113, + -102, + -89, + -95, + -96, + -103, + -86, + -117, + -99, + -97, + -101, + -93, + -84, + -86, + -99, + -86, + -101, + -93, + -92, + -117, + -99, + -95, + -100, + -98, + -99, + -103, + -101, + -88, + -98, + -93, + -99, + -117, + -82, + -100, + -96, + -101, + -100, + -88, + -99, + -117, + -91, + -101, + -117, + -117, + -90, + -97, + -117, + -90, + -117, + -102, + -97, + -87, + -84, + -93, + -99, + -84, + -90, + -96, + -87, + -117, + -103, + -86, + -101, + -117, + -97, + -95, + -118, + -96, + -117, + -97, + -98, + -95, + -98, + -91, + -90, + -117, + -98, + -100, + -98, + -84, + -100, + -96, + -98, + -86, + -96, + -96, + -103, + -95, + -85, + -96, + -95, + -98, + -100, + -99, + -100, + -87, + -101, + -100, + -101, + -117, + -99, + -84, + -99, + -99, + -99, + -100, + -98, + -90, + -101, + -98, + -101, + -117, + -85, + -93, + -86, + -92, + -101, + -94, + -87, + -118, + -95, + -101, + -89, + -93, + -102, + -92, + -91, + -96, + -94, + -98, + -97, + -100, + -102, + -101, + -84, + -93, + -92, + -88, + -89, + -89, + -101, + -101, + -117, + -86, + -118, + -117, + -100, + -117, + -101, + -98, + -98, + -100, + -97, + -99, + -98, + -89, + -100, + -99, + -102, + -93, + -102, + -98, + -101, + -92, + -101, + -99, + -101, + -117, + -100, + -90, + -102, + -99, + -98, + -104, + -99, + -94, + -96, + -117, + -97, + -99, + -102, + -117, + -98, + -99, + -92, + -100, + -84, + -90, + -97, + -87, + -91, + -99, + -117, + -97, + -116, + -98, + -87, + -94, + -97, + -97, + -97, + -117, + -89, + -118, + -96, + -100, + -118, + -101, + -85, + -94, + -92, + -93, + -89, + -117, + -84, + -95, + -100, + -103, + -87, + -93, + -117, + -87, + -96, + -101, + -86, + -96, + -104, + -92, + -93, + -99, + -117, + -98, + -90, + -98, + -96, + -99, + -100, + -117, + -90, + -101, + -117, + -102, + -94, + -98, + -103, + -87, + -99, + -102, + -85, + -88, + -118, + -100, + -99, + -90, + -95, + -99, + -89, + -85, + -117, + -97, + -85, + -99, + -94, + -116, + -94, + -100, + -117, + -96, + -117, + -84, + -97, + -118, + -97, + -100, + -94, + -101, + -118, + -85, + -97, + -118, + -100, + -84, + -93, + -98, + -98, + -85, + -94, + -99, + -97, + -118, + -99, + -94, + -104, + -89, + -99, + -99, + -97, + -95, + -100, + -96, + -117, + -84, + -94, + -88, + -89, + -97, + -87, + -99, + -103, + -86, + -101, + -91, + -98, + -102, + -97, + -118, + -116, + -117, + -98, + -100, + -95, + -99, + -100, + -117, + -95, + -99, + -87, + -93, + -82, + -97, + -87, + -117, + -94, + -96, + -97, + -99, + -97, + -98, + -92, + -99, + -100, + -101, + -95, + -96, + -117, + -97, + -90, + -118, + -117, + -98, + -99, + -97, + -92, + -118, + -117, + -96, + -101, + -98, + -85, + -86, + -87, + -85, + -117, + -91, + -101, + -102, + -100, + -101, + -99, + -98, + -100, + -97, + -96, + -99, + -100, + -99, + -100, + -118, + -100, + -89, + -100, + -98, + -100, + -98, + -101, + -94, + -93, + -102, + -100, + -99, + -87, + -117, + -99, + -86, + -97, + -117, + -101, + -98, + -98, + -87, + -97, + -95, + -99, + -118, + -117, + -95, + -87, + -101, + -87, + -83, + -118, + -101, + -102, + -98, + -97, + -95, + -117, + -97, + -88, + -98, + -117, + -92, + -118, + -98, + -99, + -102, + -117, + -98, + -117, + -96, + -99, + -117, + -100, + -97, + -98, + -117, + -99, + -118, + -90, + -99, + -98, + -91, + -117, + -102, + -105, + -95, + -117, + -118, + -100, + -99, + -95, + -99, + -84, + -115, + -103, + -100, + -94, + -82, + -99, + -100, + -98, + -100, + -103, + -96, + -98, + -96, + -101, + -83, + -84, + -117, + -98, + -99, + -91, + -98, + -100, + -90, + -103, + -118, + -89, + -96, + -98, + -89, + -86, + -98, + -97, + -83, + -89, + -85, + -99, + -87, + -90, + -100, + -95, + -102, + -91, + -91, + -96, + -97, + -100, + -95, + -117, + -101, + -100, + -99, + -84, + -90, + -96, + -86, + -88, + -89, + -86, + -98, + -102, + -99, + -100, + -86, + -99, + -117, + -98, + -103, + -101, + -89, + -118, + -98, + -98, + -99, + -100, + -101, + -98, + -89, + -101, + -93, + -85, + -98, + -97, + -96, + -97, + -100, + -102, + -102, + -118, + -91, + -118, + -101, + -118, + -99, + -97, + -84, + -101, + -99, + -94, + -117, + -93, + -96, + -96, + -100, + -101, + -100, + -89, + -100, + -118, + -93, + -95, + -96, + -89, + -91, + -92, + -100, + -99, + -83, + -93, + -117, + -99, + -100, + -101, + -99, + -96, + -99, + -88, + -97, + -103, + -117, + -117, + -99, + -117, + -88, + -95, + -98, + -100, + -98, + -94, + -87, + -102, + -102, + -98, + -92, + -98, + -89, + -98, + -97, + -95, + -102, + -85, + -88, + -91, + -99, + -100, + -101, + -99, + -101, + -117, + -103, + -93, + -94, + -117, + -97, + -117, + -100, + -102, + -99, + -99, + -93, + -99, + -88, + -102, + -102, + -118, + -118, + -93, + -118, + -100, + -99, + -100, + -118, + -88, + -101, + -99, + -96, + -91, + -89, + -99, + -118, + -97, + -90, + -102, + -100, + -97, + -117, + -102, + -95, + -100, + -91, + -93, + -100, + -88, + -101, + -100, + -93, + -98, + -93, + -118, + -101, + -98, + -118, + -83, + -97, + -118, + -99, + -117, + -96, + -99, + -118, + -117, + -95, + -118, + -101, + -118, + -83, + -92, + -100, + -86, + -102, + -97, + -102, + -94, + -89, + -104, + -117, + -102, + -101, + -98, + -100, + -118, + -94, + -117, + -96, + -99, + -100, + -97, + -99, + -101, + -99, + -98, + -99, + -100, + -99, + -100, + -96, + -98, + -100, + -88, + -94, + -102, + -96, + -97, + -101, + -118, + -86, + -98, + -117, + -95, + -100, + -117, + -98, + -94, + -99, + -118, + -118, + -93, + -118, + -98, + -118, + -102, + -117, + -88, + -97, + -83, + -101, + -100, + -89, + -99, + -96, + -96, + -102, + -101, + -89, + -99, + -99, + -100, + -98, + -118, + -97, + -117, + -102, + -98, + -99, + -93, + -100, + -97, + -99, + -103, + -87, + -91, + -97, + -118, + -99, + -99, + -85, + -99, + -99, + -100, + -98, + -101, + -97, + -98, + -91, + -98, + -99, + -97, + -90, + -103, + -95, + -101, + -90, + -88, + -88, + -85, + -96, + -118, + -117, + -118, + -98, + -98, + -95, + -90, + -117, + -100, + -99, + -100, + -117, + -101, + -97, + -117, + -102, + -101, + -98, + -97, + -93, + -94, + -118, + -117, + -100, + -84, + -97, + -118, + -101, + -118, + -102, + -102, + -96, + -102, + -92, + -99, + -97, + -98, + -103, + -99, + -118, + -88, + -99, + -87, + -92, + -99, + -96, + -98, + -87, + -95, + -96, + -99, + -98, + -88, + -101, + -117, + -94, + -99, + -94, + -94, + -92, + -99, + -100, + -100, + -99, + -95, + -100, + -96, + -118, + -101, + -100, + -101, + -99, + -99, + -102, + -91, + -118, + -99, + -117, + -98, + -100, + -117, + -89, + -100, + -101, + -95, + -118, + -96, + -95, + -91, + -101, + -93, + -84, + -91, + -97, + -117, + -99, + -102, + -98, + -98, + -96, + -98, + -118, + -99, + -93, + -98, + -100, + -83, + -118, + -102, + -100, + -92, + -118, + -99, + -98, + -96, + -94, + -98, + -99, + -84, + -99, + -99, + -87, + -97, + -99, + -90, + -91, + -101, + -100, + -91, + -118, + -102, + -118, + -89, + -88, + -90, + -99, + -101, + -118, + -92, + -89, + -118, + -88, + -99, + -101, + -95, + -95, + -100, + -97, + -102, + -91, + -99, + -91, + -99, + -118, + -98, + -100, + -118, + -117, + -103, + -101, + -117, + -99, + -117, + -118, + -93, + -102, + -99, + -88, + -117, + -97, + -99, + -97, + -117, + -118, + -100, + -94, + -96, + -93, + -101, + -102, + -98, + -100, + -118, + -93, + -100, + -96, + -99, + -98, + -99, + -118, + -99, + -99, + -102, + -96, + -99, + -100, + -102, + -102, + -99, + -101, + -96, + -117, + -96, + -100, + -104, + -99, + -118, + -99, + -101, + -104, + -100, + -99, + -98, + -98, + -99, + -100, + -99, + -93, + -95, + -87, + -92, + -90, + -103, + -98, + -88, + -86, + -118, + -102, + -98, + -85, + -102, + -100, + -117, + -101, + -102, + -91, + -99, + -88, + -103, + -91, + -82, + -97, + -101, + -95, + -118, + -81, + -94, + -100, + -90, + -101, + -117, + -97, + -99, + -99, + -103, + -118, + -101, + -93, + -99, + -94, + -102, + -99, + -99, + -88, + -99, + -89, + -101, + -118, + -94, + -96, + -87, + -92, + -117, + -101, + -100, + -100, + -117, + -101, + -97, + -100, + -99, + -101, + -96, + -102, + -96, + -118, + -97, + -84, + -98, + -97, + -85, + -117, + -101, + -100, + -99, + -88, + -98, + -95, + -85, + -102, + -102, + -103, + -95, + -117, + -96, + -96, + -88, + -102, + -117, + -90, + -85, + -100, + -94, + -101, + -100, + -103, + -100, + -85, + -93, + -93, + -94, + -95, + -98, + -99, + -102, + -103, + -99, + -117, + -89, + -100, + -100, + -100, + -97, + -91, + -89, + -99, + -95, + -101, + -117, + -117, + -92, + -93, + -92, + -100, + -91, + -100, + -98, + -103, + -97, + -98, + -100, + -95, + -98, + -103, + -100, + -94, + -100, + -118, + -103, + -117, + -96, + -90, + -101, + -99, + -91, + -100, + -101, + -97, + -101, + -100, + -99, + -83, + -118, + -99, + -92, + -96, + -88, + -99, + -82, + -99, + -98, + -89, + -97, + -92, + -99, + -98, + -92, + -90, + -95, + -98, + -100, + -100, + -101, + -96, + -100, + -117, + -97, + -100, + -96, + -101, + -98, + -97, + -99, + -97, + -89, + -118, + -97, + -99, + -91, + -87, + -97, + -94, + -100, + -100, + -100, + -98, + -102, + -99, + -92, + -98, + -91, + -88, + -98, + -96, + -90, + -100, + -103, + -101, + -88, + -92, + -86, + -101, + -86, + -98, + -84, + -98, + -89, + -99, + -99, + -117, + -97, + -100, + -99, + -100, + -94, + -89, + -99, + -98, + -100, + -117, + -89, + -91, + -117, + -99, + -118, + -101, + -92, + -97, + -118, + -100, + -91, + -101, + -117, + -86, + -98, + -96, + -100, + -100, + -100, + -100, + -89, + -91, + -103, + -101, + -87, + -117, + -87, + -86, + -99, + -93, + -102, + -97, + -100, + -102, + -90, + -100, + -91, + -97, + -93, + -91, + -89, + -103, + -117, + -117, + -96, + -99, + -117, + -98, + -100, + -102, + -85, + -91, + -97, + -99, + -117, + -100, + -88, + -98, + -100, + -100, + -101, + -98, + -99, + -101, + -94, + -102, + -99, + -117, + -87, + -101, + -93, + -91, + -118, + -101, + -95, + -100, + -93, + -99, + -102, + -99, + -98, + -101, + -117, + -117, + -99, + -89, + -101, + -85, + -98, + -99, + -99, + -92, + -102, + -102, + -97, + -94, + -99, + -97, + -100, + -95, + -92, + -102, + -90, + -90, + -118, + -99, + -102, + -99, + -100, + -104, + -95, + -96, + -100, + -96, + -99, + -99, + -101, + -88, + -102, + -101, + -94, + -94, + -118, + -99, + -97, + -85, + -88, + -96, + -101, + -92, + -101, + -98, + -99, + -102, + -99, + -92, + -100, + -98, + -99, + -89, + -99, + -89, + -99, + -89, + -96, + -87, + -100, + -101, + -94, + -99, + -101, + -88, + -99, + -102, + -102, + -101, + -92, + -98, + -85, + -100, + -100, + -98, + -97, + -99, + -87, + -100, + -98, + -92, + -97, + -89, + -100, + -93, + -100, + -101, + -98, + -89, + -118, + -89, + -100, + -99, + -87, + -91, + -101, + -99, + -95, + -99, + -95, + -99, + -91, + -92, + -86, + -89, + -97, + -100, + -98, + -117, + -98, + -100, + -99, + -101, + -98, + -99, + -99, + -101, + -97, + -88, + -98, + -85, + -97, + -97, + -101, + -93, + -98, + -100, + -99, + -99, + -97, + -99, + -95, + -102, + -87, + -101, + -89, + -102, + -90, + -101, + -90, + -101, + -101, + -90, + -100, + -102, + -98, + -98, + -99, + -89, + -100, + -98, + -85, + -99, + -99, + -96, + -117, + -99, + -86, + -101, + -92, + -97, + -98, + -100, + -84, + -94, + -102, + -101, + -87, + -95, + -86, + -102, + -98, + -93, + -91, + -117, + -100, + -93, + -98, + -101, + -98, + -84, + -100, + -101, + -99, + -86, + -96, + -92, + -83, + -100, + -99, + -85, + -101, + -100, + -99, + -94, + -104, + -87, + -117, + -98, + -87, + -98, + -98, + -90, + -91, + -98, + -93, + -98, + -101, + -92, + -92, + -99, + -101, + -86, + -87, + -100, + -101, + -99, + -101, + -97, + -104, + -90, + -101, + -85, + -103, + -100, + -99, + -101, + -101, + -92, + -102, + -100, + -100, + -104, + -98, + -99, + -97, + -102, + -89, + -99, + -91, + -102, + -101, + -97, + -98, + -100, + -88, + -93, + -87, + -101, + -100, + -103, + -96, + -91, + -101, + -91, + -98, + -100, + -103, + -99, + -98, + -98, + -102, + -102, + -99, + -100, + -89, + -101, + -98, + -99, + -99, + -100, + -97, + -95, + -87, + -101, + -100, + -87, + -85, + -102, + -86, + -96, + -91, + -94, + -99, + -98, + -93, + -92, + -100, + -100, + -101, + -103, + -100, + -91, + -103, + -99, + -101, + -98, + -91, + -103, + -100, + -85, + -99, + -100, + -100, + -100, + -100, + -91, + -99, + -114, + -100, + -92, + -105, + -88, + -90, + -101, + -96, + -99, + -99, + -94, + -100, + -96, + -97, + -105, + -96, + -100, + -102, + -94, + -99, + -99, + -89, + -85, + -87, + -105, + -105, + -87, + -97, + -98, + -97, + -99, + -97, + -98, + -87, + -99, + -89, + -99, + -91, + -97, + -98, + -95, + -90, + -98, + -97, + -96, + -98, + -98, + -101, + -100, + -100, + -99, + -97, + -90, + -97, + -98, + -98, + -96, + -97, + -97, + -96, + -97 + ], + "name": "Lon", + "lat": [ + 48, + 18, + 49, + 49, + 49, + 49, + 49, + 49, + 48, + 49, + 19, + 48, + 18, + 49, + 48, + 48, + 49, + 49, + 48, + 48, + 48, + 49, + 18, + 49, + 49, + 48, + 48, + 49, + 48, + 18, + 49, + 19, + 48, + 49, + 48, + 49, + 49, + 48, + 48, + 48, + 49, + 48, + 49, + 49, + 48, + 43, + 49, + 18, + 48, + 49, + 49, + 49, + 49, + 49, + 49, + 49, + 49, + 49, + 49, + 48, + 48, + 49, + 49, + 33, + 48, + 49, + 48, + 48, + 49, + 48, + 48, + 49, + 49, + 49, + 47, + 48, + 49, + 49, + 49, + 48, + 36, + 49, + 49, + 49, + 49, + 49, + 49, + 49, + 48, + 49, + 49, + 49, + 48, + 49, + 48, + 48, + 49, + 18, + 49, + 18, + 49, + 49, + 49, + 48, + 48, + 48, + 48, + 49, + 48, + 49, + 26, + 49, + 49, + 48, + 49, + 49, + 47, + 49, + 49, + 49, + 48, + 49, + 49, + 48, + 49, + 49, + 49, + 49, + 37, + 49, + 47, + 48, + 49, + 49, + 49, + 49, + 49, + 42, + 47, + 49, + 49, + 18, + 49, + 49, + 47, + 49, + 26, + 48, + 49, + 49, + 48, + 36, + 48, + 47, + 48, + 49, + 49, + 31, + 49, + 48, + 47, + 43, + 48, + 42, + 47, + 48, + 47, + 49, + 49, + 49, + 35, + 48, + 43, + 49, + 18, + 46, + 18, + 48, + 48, + 47, + 47, + 48, + 48, + 49, + 49, + 49, + 47, + 49, + 48, + 49, + 49, + 32, + 42, + 49, + 49, + 25, + 49, + 33, + 48, + 47, + 26, + 43, + 49, + 30, + 47, + 47, + 48, + 30, + 48, + 48, + 47, + 49, + 33, + 26, + 47, + 33, + 49, + 49, + 48, + 34, + 47, + 49, + 47, + 49, + 49, + 38, + 49, + 49, + 47, + 27, + 49, + 49, + 49, + 38, + 48, + 37, + 26, + 49, + 48, + 26, + 47, + 47, + 47, + 38, + 49, + 48, + 37, + 48, + 47, + 48, + 38, + 31, + 48, + 48, + 48, + 49, + 47, + 47, + 49, + 26, + 49, + 42, + 49, + 47, + 32, + 44, + 48, + 48, + 43, + 49, + 49, + 32, + 38, + 43, + 47, + 48, + 49, + 48, + 48, + 49, + 38, + 35, + 25, + 49, + 49, + 18, + 49, + 49, + 26, + 43, + 49, + 48, + 49, + 48, + 48, + 49, + 47, + 26, + 47, + 47, + 49, + 49, + 47, + 49, + 49, + 48, + 49, + 43, + 49, + 37, + 47, + 49, + 42, + 47, + 49, + 48, + 48, + 19, + 46, + 49, + 18, + 38, + 47, + 39, + 47, + 31, + 47, + 47, + 48, + 38, + 41, + 25, + 47, + 49, + 42, + 48, + 48, + 47, + 47, + 48, + 48, + 49, + 38, + 38, + 43, + 48, + 48, + 37, + 44, + 43, + 19, + 48, + 48, + 33, + 45, + 25, + 43, + 47, + 49, + 49, + 38, + 47, + 44, + 47, + 43, + 26, + 31, + 47, + 47, + 49, + 49, + 40, + 48, + 44, + 42, + 47, + 18, + 34, + 47, + 48, + 47, + 40, + 47, + 37, + 45, + 37, + 48, + 35, + 40, + 40, + 48, + 32, + 38, + 44, + 48, + 48, + 49, + 37, + 44, + 48, + 41, + 47, + 49, + 49, + 38, + 43, + 49, + 47, + 48, + 44, + 34, + 49, + 48, + 47, + 48, + 26, + 48, + 48, + 49, + 42, + 47, + 41, + 43, + 37, + 48, + 26, + 49, + 46, + 48, + 34, + 42, + 38, + 48, + 44, + 48, + 33, + 48, + 37, + 30, + 49, + 47, + 37, + 48, + 48, + 48, + 47, + 49, + 48, + 33, + 48, + 26, + 49, + 30, + 19, + 43, + 48, + 42, + 48, + 42, + 26, + 43, + 27, + 37, + 26, + 37, + 48, + 48, + 43, + 35, + 42, + 48, + 44, + 45, + 38, + 30, + 48, + 48, + 25, + 27, + 44, + 49, + 45, + 47, + 49, + 42, + 18, + 48, + 33, + 48, + 43, + 43, + 44, + 26, + 30, + 26, + 38, + 43, + 48, + 48, + 34, + 49, + 47, + 49, + 37, + 47, + 49, + 35, + 49, + 27, + 26, + 41, + 43, + 30, + 25, + 26, + 41, + 41, + 43, + 48, + 44, + 48, + 26, + 48, + 35, + 48, + 35, + 47, + 47, + 37, + 34, + 49, + 47, + 48, + 37, + 47, + 42, + 33, + 25, + 35, + 44, + 41, + 26, + 47, + 38, + 44, + 48, + 44, + 49, + 48, + 31, + 25, + 48, + 43, + 35, + 48, + 43, + 48, + 48, + 43, + 49, + 44, + 30, + 42, + 49, + 48, + 48, + 36, + 48, + 27, + 40, + 48, + 27, + 44, + 25, + 26, + 35, + 37, + 48, + 43, + 41, + 37, + 49, + 49, + 47, + 44, + 43, + 47, + 49, + 47, + 46, + 44, + 46, + 30, + 47, + 38, + 48, + 47, + 38, + 49, + 48, + 47, + 42, + 48, + 44, + 48, + 49, + 37, + 43, + 30, + 35, + 45, + 49, + 48, + 46, + 26, + 48, + 49, + 43, + 46, + 49, + 48, + 44, + 34, + 44, + 47, + 48, + 43, + 33, + 49, + 43, + 43, + 38, + 48, + 44, + 30, + 37, + 49, + 42, + 43, + 47, + 42, + 46, + 47, + 42, + 48, + 48, + 42, + 44, + 42, + 42, + 43, + 38, + 35, + 38, + 25, + 25, + 48, + 38, + 46, + 30, + 35, + 37, + 30, + 47, + 47, + 47, + 46, + 48, + 42, + 43, + 48, + 48, + 27, + 34, + 42, + 38, + 38, + 49, + 44, + 34, + 48, + 47, + 33, + 42, + 25, + 42, + 43, + 42, + 48, + 25, + 43, + 29, + 37, + 45, + 35, + 42, + 47, + 48, + 32, + 48, + 35, + 46, + 38, + 29, + 38, + 33, + 42, + 47, + 48, + 35, + 48, + 49, + 27, + 29, + 49, + 49, + 42, + 44, + 35, + 25, + 37, + 27, + 38, + 48, + 42, + 35, + 48, + 48, + 43, + 40, + 48, + 38, + 49, + 27, + 26, + 48, + 44, + 43, + 30, + 45, + 48, + 47, + 46, + 42, + 27, + 33, + 40, + 35, + 47, + 48, + 49, + 48, + 25, + 33, + 40, + 48, + 47, + 30, + 26, + 44, + 47, + 47, + 47, + 43, + 48, + 47, + 35, + 37, + 26, + 26, + 49, + 26, + 41, + 48, + 45, + 42, + 42, + 47, + 44, + 35, + 49, + 48, + 27, + 43, + 49, + 38, + 30, + 26, + 48, + 29, + 47, + 32, + 27, + 42, + 37, + 40, + 45, + 47, + 43, + 48, + 47, + 37, + 45, + 48, + 48, + 43, + 35, + 48, + 44, + 26, + 42, + 26, + 37, + 30, + 26, + 27, + 27, + 33, + 43, + 49, + 40, + 48, + 42, + 30, + 37, + 49, + 42, + 46, + 25, + 48, + 38, + 38, + 43, + 48, + 44, + 48, + 35, + 30, + 37, + 37, + 41, + 44, + 42, + 43, + 30, + 48, + 42, + 46, + 43, + 37, + 42, + 30, + 49, + 26, + 47, + 33, + 47, + 49, + 49, + 48, + 33, + 41, + 47, + 25, + 41, + 33, + 38, + 40, + 38, + 49, + 44, + 48, + 39, + 48, + 42, + 45, + 36, + 31, + 41, + 49, + 38, + 35, + 42, + 35, + 38, + 34, + 44, + 38, + 31, + 43, + 49, + 44, + 35, + 49, + 35, + 38, + 35, + 40, + 42, + 31, + 47, + 48, + 47, + 45, + 42, + 35, + 38, + 42, + 35, + 47, + 30, + 48, + 28, + 43, + 35, + 48, + 48, + 25, + 38, + 44, + 47, + 46, + 35, + 42, + 42, + 44, + 26, + 35, + 47, + 37, + 49, + 34, + 29, + 42, + 48, + 47, + 44, + 48, + 31, + 48, + 44, + 48, + 48, + 43, + 38, + 48, + 48, + 42, + 42, + 44, + 43, + 42, + 45, + 49, + 44, + 49, + 48, + 48, + 41, + 26, + 44, + 40, + 49, + 41, + 41, + 42, + 35, + 43, + 27, + 39, + 48, + 30, + 37, + 30, + 35, + 45, + 26, + 46, + 30, + 45, + 25, + 43, + 47, + 43, + 47, + 48, + 22, + 48, + 30, + 45, + 42, + 44, + 44, + 40, + 30, + 39, + 46, + 47, + 25, + 48, + 48, + 42, + 35, + 42, + 46, + 48, + 28, + 41, + 48, + 35, + 40, + 43, + 27, + 43, + 43, + 33, + 44, + 48, + 44, + 25, + 45, + 42, + 32, + 18, + 38, + 46, + 35, + 42, + 34, + 48, + 40, + 47, + 30, + 43, + 38, + 44, + 44, + 34, + 38, + 38, + 33, + 44, + 38, + 36, + 44, + 36, + 30, + 48, + 46, + 49, + 31, + 31, + 44, + 49, + 45, + 43, + 41, + 33, + 33, + 42, + 45, + 42, + 41, + 46, + 44, + 38, + 42, + 47, + 41, + 44, + 44, + 48, + 25, + 43, + 44, + 41, + 42, + 35, + 35, + 47, + 30, + 43, + 37, + 35, + 30, + 46, + 35, + 47, + 42, + 41, + 44, + 28, + 49, + 37, + 35, + 41, + 26, + 49, + 41, + 35, + 35, + 26, + 39, + 41, + 44, + 47, + 25, + 41, + 40, + 46, + 48, + 30, + 30, + 43, + 41, + 47, + 45, + 40, + 35, + 35, + 47, + 42, + 48, + 45, + 29, + 42, + 47, + 47, + 26, + 38, + 46, + 28, + 44, + 36, + 35, + 42, + 36, + 38, + 26, + 35, + 18, + 42, + 38, + 42, + 27, + 48, + 39, + 32, + 44, + 29, + 35, + 47, + 27, + 46, + 45, + 48, + 43, + 40, + 28, + 44, + 37, + 38, + 49, + 48, + 37, + 28, + 43, + 43, + 28, + 40, + 27, + 32, + 25, + 36, + 41, + 49, + 38, + 46, + 45, + 25, + 43, + 45, + 37, + 42, + 41, + 29, + 44, + 31, + 49, + 47, + 47, + 48, + 44, + 35, + 47, + 34, + 48, + 48, + 45, + 34, + 47, + 49, + 37, + 41, + 47, + 43, + 48, + 43, + 43, + 32, + 46, + 43, + 43, + 35, + 38, + 35, + 44, + 40, + 37, + 34, + 41, + 40, + 36, + 48, + 48, + 42, + 48, + 30, + 30, + 42, + 47, + 49, + 31, + 44, + 45, + 48, + 43, + 30, + 44, + 32, + 41, + 44, + 30, + 47, + 39, + 46, + 48, + 40, + 42, + 47, + 45, + 43, + 49, + 49, + 35, + 42, + 34, + 35, + 39, + 49, + 46, + 42, + 38, + 42, + 45, + 47, + 44, + 47, + 44, + 48, + 31, + 47, + 33, + 43, + 43, + 45, + 46, + 27, + 48, + 30, + 27, + 49, + 27, + 47, + 33, + 43, + 47, + 48, + 42, + 41, + 39, + 30, + 38, + 42, + 37, + 35, + 45, + 43, + 34, + 41, + 38, + 41, + 44, + 46, + 29, + 44, + 37, + 48, + 41, + 47, + 30, + 38, + 35, + 49, + 35, + 34, + 28, + 41, + 44, + 45, + 41, + 40, + 26, + 41, + 48, + 47, + 29, + 42, + 30, + 49, + 47, + 49, + 42, + 43, + 48, + 30, + 41, + 47, + 25, + 38, + 48, + 49, + 39, + 48, + 42, + 42, + 48, + 30, + 35, + 43, + 37, + 35, + 30, + 42, + 41, + 37, + 44, + 48, + 42, + 35, + 38, + 43, + 30, + 34, + 28, + 33, + 26, + 41, + 27, + 43, + 41, + 40, + 49, + 49, + 46, + 47, + 45, + 26, + 44, + 30, + 30, + 46, + 35, + 48, + 49, + 44, + 47, + 44, + 48, + 35, + 48, + 42, + 45, + 47, + 46, + 46, + 42, + 48, + 43, + 47, + 30, + 47, + 44, + 42, + 42, + 47, + 27, + 46, + 37, + 48, + 44, + 38, + 43, + 48, + 38, + 49, + 38, + 33, + 26, + 25, + 41, + 35, + 46, + 30, + 42, + 44, + 48, + 48, + 37, + 48, + 28, + 49, + 35, + 49, + 44, + 30, + 33, + 47, + 44, + 38, + 38, + 28, + 48, + 41, + 31, + 38, + 26, + 30, + 48, + 34, + 43, + 40, + 45, + 29, + 46, + 44, + 37, + 32, + 42, + 30, + 41, + 48, + 33, + 47, + 43, + 47, + 49, + 46, + 49, + 36, + 43, + 35, + 33, + 43, + 42, + 42, + 38, + 45, + 30, + 26, + 42, + 48, + 49, + 44, + 44, + 35, + 49, + 40, + 44, + 44, + 49, + 37, + 49, + 36, + 35, + 44, + 49, + 45, + 33, + 28, + 42, + 42, + 42, + 44, + 43, + 29, + 41, + 38, + 36, + 44, + 31, + 44, + 30, + 44, + 49, + 46, + 38, + 29, + 30, + 45, + 48, + 33, + 40, + 42, + 44, + 43, + 45, + 41, + 30, + 26, + 44, + 47, + 44, + 43, + 30, + 42, + 43, + 46, + 34, + 44, + 48, + 49, + 41, + 45, + 44, + 47, + 46, + 30, + 38, + 45, + 29, + 26, + 39, + 35, + 27, + 33, + 49, + 29, + 43, + 47, + 43, + 49, + 45, + 35, + 30, + 31, + 41, + 47, + 35, + 47, + 37, + 47, + 47, + 29, + 31, + 40, + 25, + 44, + 48, + 46, + 35, + 43, + 41, + 30, + 32, + 26, + 29, + 39, + 37, + 38, + 45, + 37, + 49, + 46, + 49, + 43, + 44, + 27, + 44, + 46, + 33, + 41, + 45, + 48, + 41, + 42, + 42, + 46, + 46, + 38, + 41, + 35, + 46, + 48, + 44, + 43, + 34, + 44, + 49, + 44, + 35, + 38, + 47, + 49, + 41, + 37, + 48, + 41, + 37, + 42, + 48, + 28, + 44, + 27, + 43, + 45, + 38, + 31, + 47, + 41, + 42, + 42, + 44, + 46, + 30, + 35, + 48, + 42, + 48, + 37, + 35, + 44, + 41, + 46, + 47, + 41, + 46, + 47, + 48, + 46, + 47, + 29, + 47, + 44, + 41, + 42, + 45, + 27, + 41, + 47, + 43, + 38, + 42, + 47, + 46, + 47, + 43, + 35, + 27, + 42, + 40, + 37, + 29, + 41, + 46, + 37, + 41, + 27, + 40, + 47, + 46, + 47, + 38, + 46, + 45, + 41, + 40, + 47, + 44, + 47, + 47, + 35, + 44, + 35, + 43, + 42, + 26, + 48, + 29, + 42, + 42, + 31, + 41, + 41, + 44, + 38, + 33, + 30, + 46, + 47, + 43, + 45, + 27, + 49, + 35, + 44, + 42, + 48, + 39, + 44, + 26, + 32, + 31, + 32, + 35, + 43, + 38, + 48, + 46, + 26, + 43, + 44, + 28, + 39, + 47, + 46, + 26, + 43, + 29, + 42, + 47, + 41, + 46, + 40, + 44, + 46, + 29, + 47, + 33, + 47, + 37, + 42, + 35, + 40, + 35, + 42, + 46, + 42, + 48, + 41, + 43, + 29, + 41, + 49, + 35, + 38, + 27, + 47, + 42, + 43, + 46, + 41, + 41, + 43, + 42, + 31, + 46, + 40, + 47, + 34, + 43, + 29, + 38, + 41, + 46, + 43, + 44, + 43, + 40, + 43, + 49, + 36, + 43, + 25, + 47, + 38, + 42, + 36, + 43, + 48, + 30, + 35, + 25, + 44, + 33, + 30, + 48, + 49, + 37, + 48, + 42, + 26, + 30, + 48, + 48, + 49, + 44, + 43, + 48, + 46, + 41, + 40, + 30, + 40, + 49, + 30, + 43, + 26, + 36, + 46, + 44, + 42, + 46, + 42, + 38, + 45, + 29, + 47, + 46, + 30, + 26, + 39, + 46, + 47, + 39, + 42, + 26, + 41, + 26, + 41, + 41, + 37, + 37, + 44, + 44, + 44, + 31, + 41, + 47, + 44, + 47, + 27, + 27, + 45, + 27, + 34, + 41, + 37, + 47, + 46, + 46, + 40, + 46, + 27, + 30, + 47, + 42, + 25, + 30, + 30, + 49, + 35, + 35, + 40, + 39, + 31, + 48, + 30, + 41, + 37, + 45, + 47, + 46, + 30, + 36, + 37, + 43, + 47, + 49, + 45, + 44, + 42, + 41, + 45, + 39, + 43, + 29, + 39, + 49, + 46, + 43, + 41, + 44, + 47, + 30, + 49, + 37, + 49, + 35, + 38, + 43, + 38, + 43, + 49, + 48, + 36, + 37, + 42, + 35, + 41, + 41, + 41, + 30, + 47, + 28, + 49, + 26, + 38, + 30, + 44, + 35, + 34, + 27, + 42, + 41, + 41, + 36, + 29, + 32, + 30, + 49, + 45, + 37, + 38, + 47, + 36, + 41, + 38, + 43, + 47, + 46, + 39, + 43, + 35, + 48, + 44, + 34, + 37, + 43, + 35, + 43, + 30, + 45, + 41, + 47, + 41, + 43, + 42, + 45, + 40, + 32, + 42, + 31, + 29, + 42, + 46, + 27, + 27, + 44, + 43, + 25, + 47, + 46, + 43, + 29, + 43, + 48, + 37, + 30, + 29, + 40, + 42, + 37, + 41, + 27, + 47, + 30, + 42, + 27, + 44, + 42, + 30, + 30, + 30, + 32, + 30, + 40, + 45, + 41, + 37, + 30, + 42, + 44, + 49, + 49, + 49, + 45, + 43, + 36, + 43, + 44, + 41, + 43, + 49, + 38, + 38, + 30, + 49, + 42, + 30, + 43, + 33, + 46, + 41, + 35, + 44, + 41, + 26, + 42, + 45, + 44, + 46, + 48, + 35, + 33, + 44, + 40, + 45, + 49, + 43, + 42, + 45, + 25, + 27, + 47, + 34, + 46, + 33, + 47, + 46, + 35, + 46, + 41, + 39, + 42, + 48, + 40, + 46, + 40, + 46, + 47, + 40, + 26, + 49, + 27, + 26, + 42, + 43, + 28, + 44, + 32, + 41, + 46, + 43, + 41, + 32, + 43, + 38, + 49, + 44, + 34, + 30, + 41, + 31, + 35, + 41, + 46, + 44, + 39, + 30, + 39, + 45, + 47, + 45, + 48, + 46, + 26, + 42, + 45, + 46, + 32, + 26, + 45, + 42, + 37, + 40, + 39, + 45, + 44, + 46, + 40, + 38, + 37, + 44, + 27, + 44, + 27, + 34, + 46, + 32, + 49, + 41, + 46, + 40, + 46, + 36, + 30, + 41, + 34, + 49, + 47, + 33, + 46, + 44, + 35, + 45, + 43, + 43, + 44, + 48, + 40, + 49, + 26, + 49, + 42, + 47, + 39, + 42, + 44, + 39, + 38, + 40, + 38, + 33, + 34, + 46, + 42, + 41, + 34, + 49, + 29, + 34, + 26, + 40, + 35, + 41, + 45, + 32, + 34, + 47, + 43, + 25, + 39, + 47, + 37, + 43, + 43, + 35, + 32, + 41, + 41, + 30, + 45, + 44, + 38, + 43, + 41, + 43, + 38, + 49, + 28, + 48, + 47, + 46, + 42, + 42, + 48, + 43, + 43, + 30, + 42, + 41, + 46, + 42, + 45, + 45, + 35, + 46, + 47, + 45, + 47, + 44, + 41, + 46, + 32, + 43, + 46, + 43, + 49, + 43, + 42, + 44, + 47, + 29, + 26, + 43, + 41, + 45, + 41, + 34, + 46, + 35, + 47, + 37, + 27, + 34, + 34, + 27, + 45, + 26, + 38, + 35, + 35, + 49, + 42, + 42, + 49, + 50, + 43, + 48, + 44, + 43, + 40, + 47, + 38, + 44, + 45, + 44, + 44, + 41, + 33, + 44, + 45, + 46, + 40, + 47, + 42, + 41, + 43, + 40, + 46, + 39, + 33, + 49, + 41, + 25, + 40, + 46, + 33, + 41, + 39, + 38, + 48, + 31, + 39, + 47, + 42, + 45, + 44, + 43, + 45, + 46, + 38, + 47, + 47, + 46, + 44, + 30, + 29, + 42, + 49, + 35, + 41, + 45, + 34, + 26, + 41, + 41, + 30, + 32, + 29, + 41, + 39, + 44, + 45, + 42, + 41, + 34, + 32, + 35, + 31, + 32, + 35, + 33, + 31, + 47, + 46, + 44, + 46, + 47, + 27, + 42, + 41, + 41, + 39, + 40, + 39, + 31, + 39, + 43, + 27, + 35, + 27, + 25, + 46, + 44, + 43, + 31, + 41, + 41, + 41, + 44, + 46, + 40, + 40, + 46, + 42, + 33, + 48, + 36, + 36, + 46, + 30, + 42, + 39, + 35, + 39, + 39, + 46, + 36, + 42, + 30, + 46, + 41, + 36, + 35, + 37, + 31, + 32, + 41, + 41, + 46, + 30, + 34, + 39, + 39, + 41, + 35, + 44, + 42, + 49, + 30, + 49, + 36, + 28, + 45, + 35, + 38, + 31, + 38, + 42, + 34, + 44, + 44, + 44, + 46, + 44, + 43, + 44, + 42, + 43, + 34, + 39, + 42, + 45, + 32, + 42, + 41, + 37, + 43, + 45, + 43, + 43, + 35, + 45, + 46, + 34, + 34, + 38, + 47, + 41, + 40, + 46, + 47, + 33, + 43, + 44, + 35, + 41, + 48, + 46, + 39, + 37, + 38, + 46, + 38, + 42, + 29, + 44, + 41, + 38, + 35, + 46, + 38, + 28, + 36, + 41, + 36, + 27, + 34, + 29, + 36, + 33, + 32, + 43, + 45, + 49, + 40, + 46, + 35, + 46, + 44, + 39, + 38, + 46, + 29, + 47, + 44, + 47, + 39, + 41, + 41, + 33, + 43, + 35, + 27, + 38, + 38, + 43, + 43, + 47, + 45, + 35, + 27, + 43, + 33, + 49, + 44, + 48, + 46, + 45, + 42, + 30, + 31, + 30, + 45, + 43, + 35, + 29, + 45, + 44, + 46, + 46, + 40, + 42, + 44, + 43, + 41, + 45, + 39, + 44, + 27, + 33, + 38, + 29, + 34, + 36, + 28, + 30, + 27, + 43, + 49, + 41, + 30, + 28, + 48, + 44, + 42, + 39, + 44, + 42, + 31, + 46, + 33, + 31, + 27, + 45, + 41, + 47, + 38, + 45, + 35, + 36, + 49, + 42, + 40, + 32, + 35, + 36, + 43, + 30, + 41, + 42, + 37, + 44, + 33, + 49, + 47, + 32, + 29, + 46, + 46, + 27, + 30, + 34, + 46, + 36, + 42, + 41, + 46, + 44, + 38, + 28, + 41, + 33, + 46, + 30, + 44, + 46, + 38, + 47, + 40, + 45, + 34, + 39, + 29, + 46, + 32, + 35, + 35, + 32, + 29, + 42, + 32, + 33, + 47, + 39, + 46, + 37, + 30, + 43, + 29, + 38, + 45, + 41, + 42, + 38, + 35, + 46, + 42, + 46, + 44, + 46, + 47, + 35, + 48, + 31, + 30, + 42, + 40, + 39, + 44, + 43, + 40, + 28, + 29, + 40, + 43, + 30, + 40, + 43, + 41, + 29, + 42, + 42, + 42, + 44, + 45, + 36, + 47, + 37, + 30, + 40, + 26, + 30, + 43, + 39, + 44, + 46, + 36, + 47, + 42, + 42, + 41, + 31, + 41, + 43, + 44, + 38, + 40, + 47, + 44, + 47, + 31, + 44, + 49, + 30, + 38, + 45, + 30, + 39, + 43, + 38, + 48, + 46, + 29, + 36, + 43, + 40, + 30, + 34, + 47, + 34, + 43, + 43, + 29, + 40, + 41, + 43, + 46, + 37, + 43, + 44, + 31, + 49, + 45, + 34, + 44, + 45, + 36, + 41, + 38, + 43, + 46, + 29, + 41, + 29, + 40, + 43, + 49, + 42, + 26, + 49, + 39, + 44, + 43, + 34, + 38, + 44, + 35, + 35, + 45, + 36, + 26, + 33, + 46, + 45, + 42, + 34, + 42, + 41, + 43, + 46, + 39, + 41, + 29, + 49, + 40, + 50, + 49, + 45, + 38, + 40, + 42, + 47, + 45, + 47, + 47, + 41, + 44, + 44, + 31, + 38, + 41, + 43, + 44, + 32, + 46, + 39, + 28, + 49, + 49, + 41, + 41, + 46, + 44, + 37, + 35, + 35, + 40, + 30, + 36, + 46, + 37, + 41, + 35, + 40, + 38, + 35, + 41, + 28, + 35, + 41, + 40, + 30, + 28, + 37, + 38, + 42, + 35, + 41, + 29, + 40, + 40, + 27, + 44, + 39, + 46, + 44, + 47, + 45, + 29, + 40, + 43, + 45, + 27, + 38, + 31, + 34, + 32, + 34, + 46, + 28, + 45, + 32, + 37, + 35, + 49, + 47, + 35, + 40, + 46, + 46, + 35, + 34, + 46, + 47, + 29, + 44, + 41, + 48, + 47, + 46, + 46, + 46, + 40, + 47, + 41, + 35, + 29, + 42, + 47, + 35, + 35, + 30, + 49, + 45, + 39, + 48, + 43, + 42, + 44, + 38, + 40, + 42, + 29, + 28, + 43, + 26, + 29, + 41, + 44, + 46, + 34, + 42, + 49, + 33, + 30, + 29, + 41, + 41, + 39, + 26, + 46, + 36, + 33, + 44, + 45, + 40, + 42, + 25, + 40, + 38, + 42, + 42, + 38, + 40, + 26, + 39, + 40, + 41, + 40, + 42, + 29, + 46, + 31, + 34, + 38, + 28, + 46, + 40, + 31, + 39, + 46, + 25, + 43, + 43, + 42, + 32, + 41, + 47, + 42, + 36, + 26, + 40, + 30, + 45, + 46, + 49, + 44, + 39, + 44, + 33, + 39, + 47, + 46, + 28, + 46, + 47, + 45, + 47, + 41, + 29, + 37, + 32, + 42, + 42, + 40, + 45, + 43, + 37, + 29, + 43, + 50, + 50, + 39, + 34, + 31, + 43, + 46, + 42, + 46, + 31, + 46, + 46, + 40, + 41, + 39, + 35, + 38, + 44, + 35, + 42, + 45, + 45, + 34, + 29, + 42, + 43, + 47, + 42, + 28, + 47, + 27, + 41, + 41, + 43, + 47, + 47, + 45, + 29, + 47, + 30, + 46, + 40, + 47, + 49, + 41, + 47, + 26, + 31, + 47, + 49, + 42, + 47, + 29, + 39, + 38, + 34, + 30, + 44, + 45, + 39, + 27, + 41, + 35, + 46, + 41, + 40, + 42, + 44, + 29, + 40, + 47, + 43, + 49, + 35, + 49, + 47, + 41, + 36, + 41, + 41, + 33, + 34, + 42, + 39, + 47, + 44, + 44, + 31, + 40, + 28, + 45, + 42, + 42, + 44, + 40, + 36, + 45, + 47, + 46, + 45, + 35, + 42, + 45, + 29, + 44, + 34, + 38, + 46, + 28, + 42, + 44, + 43, + 43, + 30, + 44, + 44, + 43, + 46, + 45, + 42, + 31, + 27, + 40, + 46, + 41, + 34, + 31, + 35, + 44, + 50, + 44, + 34, + 31, + 33, + 34, + 42, + 36, + 41, + 43, + 41, + 42, + 29, + 35, + 31, + 46, + 41, + 43, + 38, + 44, + 26, + 46, + 35, + 42, + 27, + 43, + 45, + 41, + 43, + 43, + 34, + 36, + 29, + 46, + 40, + 43, + 47, + 27, + 31, + 29, + 47, + 31, + 41, + 27, + 38, + 36, + 38, + 45, + 41, + 34, + 32, + 45, + 41, + 40, + 40, + 40, + 47, + 34, + 46, + 38, + 41, + 50, + 32, + 46, + 46, + 26, + 41, + 46, + 36, + 41, + 46, + 30, + 35, + 35, + 40, + 44, + 30, + 41, + 46, + 46, + 42, + 43, + 36, + 37, + 35, + 39, + 44, + 41, + 44, + 40, + 34, + 44, + 29, + 30, + 41, + 46, + 48, + 38, + 46, + 38, + 40, + 34, + 28, + 38, + 39, + 27, + 42, + 28, + 43, + 35, + 49, + 30, + 40, + 47, + 40, + 43, + 43, + 41, + 40, + 39, + 41, + 49, + 44, + 32, + 34, + 44, + 44, + 45, + 42, + 37, + 45, + 29, + 39, + 41, + 30, + 35, + 46, + 44, + 37, + 45, + 38, + 44, + 35, + 47, + 46, + 49, + 44, + 45, + 43, + 41, + 30, + 30, + 29, + 49, + 45, + 39, + 45, + 47, + 45, + 30, + 32, + 39, + 44, + 34, + 38, + 34, + 32, + 44, + 42, + 45, + 44, + 41, + 43, + 39, + 35, + 46, + 40, + 28, + 42, + 31, + 43, + 25, + 44, + 39, + 42, + 39, + 43, + 29, + 49, + 41, + 28, + 44, + 40, + 41, + 40, + 45, + 46, + 35, + 40, + 40, + 43, + 47, + 43, + 46, + 44, + 46, + 40, + 41, + 41, + 50, + 46, + 45, + 47, + 41, + 45, + 47, + 35, + 41, + 29, + 35, + 39, + 39, + 47, + 39, + 30, + 40, + 42, + 49, + 49, + 45, + 41, + 38, + 39, + 29, + 40, + 43, + 39, + 36, + 35, + 29, + 44, + 42, + 39, + 40, + 43, + 49, + 31, + 29, + 44, + 27, + 47, + 49, + 44, + 37, + 45, + 40, + 46, + 40, + 31, + 41, + 40, + 47, + 47, + 30, + 40, + 41, + 46, + 36, + 42, + 37, + 46, + 42, + 38, + 43, + 35, + 40, + 35, + 44, + 40, + 40, + 30, + 27, + 46, + 46, + 35, + 45, + 47, + 40, + 41, + 32, + 29, + 28, + 37, + 46, + 41, + 42, + 38, + 29, + 45, + 29, + 27, + 38, + 44, + 34, + 43, + 41, + 46, + 32, + 38, + 46, + 29, + 40, + 46, + 40, + 40, + 44, + 38, + 29, + 45, + 31, + 41, + 40, + 49, + 41, + 27, + 40, + 45, + 44, + 40, + 43, + 40, + 39, + 41, + 42, + 46, + 38, + 31, + 41, + 41, + 46, + 35, + 46, + 28, + 31, + 42, + 42, + 45, + 44, + 30, + 42, + 35, + 41, + 31, + 46, + 42, + 47, + 44, + 35, + 41, + 46, + 43, + 46, + 47, + 43, + 46, + 41, + 39, + 50, + 47, + 47, + 43, + 44, + 46, + 40, + 35, + 28, + 30, + 42, + 44, + 34, + 43, + 46, + 45, + 45, + 44, + 46, + 46, + 43, + 35, + 35, + 42, + 31, + 31, + 27, + 41, + 35, + 36, + 29, + 41, + 39, + 40, + 44, + 40, + 45, + 46, + 34, + 41, + 46, + 40, + 46, + 41, + 40, + 46, + 46, + 35, + 31, + 33, + 41, + 36, + 42, + 35, + 49, + 46, + 46, + 49, + 40, + 26, + 31, + 40, + 42, + 41, + 40, + 38, + 27, + 28, + 45, + 40, + 28, + 39, + 39, + 31, + 33, + 41, + 46, + 29, + 40, + 40, + 31, + 34, + 34, + 36, + 46, + 31, + 30, + 42, + 37, + 38, + 38, + 45, + 42, + 33, + 40, + 42, + 39, + 33, + 43, + 45, + 43, + 34, + 38, + 40, + 37, + 33, + 37, + 46, + 38, + 34, + 36, + 45, + 42, + 46, + 31, + 29, + 39, + 39, + 42, + 29, + 30, + 38, + 30, + 43, + 42, + 27, + 34, + 35, + 42, + 42, + 30, + 46, + 40, + 41, + 45, + 30, + 41, + 44, + 35, + 45, + 42, + 29, + 33, + 36, + 42, + 35, + 43, + 40, + 29, + 44, + 40, + 44, + 47, + 41, + 42, + 47, + 49, + 35, + 26, + 49, + 35, + 33, + 46, + 41, + 41, + 40, + 43, + 35, + 40, + 39, + 39, + 34, + 42, + 27, + 28, + 44, + 29, + 45, + 44, + 29, + 48, + 38, + 27, + 36, + 31, + 44, + 38, + 29, + 43, + 29, + 40, + 40, + 25, + 39, + 44, + 40, + 36, + 38, + 39, + 34, + 46, + 50, + 38, + 38, + 49, + 35, + 27, + 33, + 27, + 50, + 35, + 29, + 45, + 45, + 45, + 40, + 44, + 46, + 42, + 41, + 26, + 41, + 28, + 35, + 30, + 41, + 50, + 39, + 34, + 41, + 35, + 37, + 40, + 44, + 47, + 44, + 35, + 45, + 33, + 50, + 41, + 40, + 43, + 42, + 33, + 46, + 45, + 38, + 31, + 40, + 27, + 43, + 47, + 45, + 46, + 34, + 46, + 41, + 41, + 47, + 27, + 45, + 44, + 34, + 49, + 41, + 40, + 41, + 31, + 31, + 29, + 41, + 27, + 41, + 39, + 45, + 35, + 29, + 46, + 40, + 41, + 41, + 44, + 40, + 28, + 32, + 38, + 39, + 44, + 40, + 45, + 31, + 46, + 39, + 27, + 38, + 35, + 35, + 46, + 41, + 37, + 30, + 31, + 49, + 46, + 42, + 47, + 49, + 46, + 35, + 40, + 44, + 46, + 45, + 40, + 27, + 41, + 45, + 46, + 28, + 27, + 41, + 43, + 46, + 47, + 41, + 39, + 38, + 27, + 40, + 44, + 46, + 40, + 41, + 41, + 40, + 41, + 40, + 33, + 41, + 41, + 38, + 31, + 46, + 44, + 32, + 43, + 28, + 49, + 46, + 42, + 46, + 46, + 39, + 45, + 27, + 41, + 37, + 41, + 46, + 32, + 40, + 46, + 47, + 45, + 34, + 45, + 40, + 41, + 46, + 38, + 34, + 31, + 46, + 25, + 44, + 30, + 31, + 35, + 39, + 45, + 45, + 44, + 35, + 27, + 28, + 46, + 35, + 33, + 31, + 45, + 45, + 49, + 40, + 41, + 42, + 42, + 39, + 42, + 46, + 42, + 43, + 44, + 43, + 42, + 37, + 45, + 34, + 35, + 29, + 43, + 27, + 41, + 37, + 45, + 44, + 46, + 41, + 41, + 42, + 38, + 47, + 47, + 47, + 45, + 46, + 35, + 41, + 43, + 45, + 40, + 44, + 46, + 28, + 40, + 46, + 30, + 31, + 34, + 40, + 33, + 46, + 49, + 41, + 46, + 43, + 37, + 46, + 38, + 45, + 30, + 30, + 46, + 27, + 36, + 44, + 44, + 35, + 34, + 35, + 41, + 41, + 44, + 37, + 42, + 30, + 49, + 31, + 42, + 42, + 40, + 40, + 41, + 40, + 28, + 29, + 30, + 44, + 45, + 33, + 49, + 49, + 44, + 46, + 36, + 35, + 37, + 44, + 45, + 29, + 37, + 45, + 37, + 45, + 41, + 36, + 31, + 34, + 42, + 29, + 42, + 47, + 39, + 36, + 45, + 41, + 35, + 40, + 43, + 44, + 31, + 46, + 42, + 27, + 41, + 42, + 31, + 45, + 40, + 36, + 41, + 50, + 40, + 35, + 42, + 40, + 27, + 38, + 29, + 32, + 37, + 45, + 33, + 35, + 42, + 46, + 39, + 32, + 35, + 35, + 45, + 44, + 50, + 39, + 31, + 41, + 38, + 35, + 40, + 42, + 29, + 46, + 42, + 47, + 33, + 44, + 34, + 39, + 41, + 50, + 50, + 28, + 45, + 33, + 31, + 41, + 28, + 30, + 26, + 26, + 42, + 29, + 45, + 37, + 41, + 45, + 45, + 44, + 33, + 46, + 40, + 50, + 40, + 43, + 47, + 37, + 49, + 43, + 40, + 45, + 46, + 45, + 46, + 44, + 30, + 27, + 44, + 41, + 47, + 40, + 28, + 34, + 28, + 46, + 41, + 41, + 34, + 40, + 35, + 37, + 35, + 40, + 29, + 45, + 40, + 29, + 35, + 39, + 46, + 31, + 38, + 29, + 28, + 45, + 33, + 37, + 41, + 39, + 30, + 46, + 30, + 45, + 39, + 45, + 41, + 41, + 33, + 39, + 47, + 43, + 43, + 29, + 49, + 44, + 40, + 27, + 45, + 31, + 34, + 39, + 44, + 40, + 47, + 27, + 45, + 27, + 35, + 41, + 31, + 42, + 27, + 42, + 46, + 34, + 34, + 39, + 30, + 40, + 40, + 46, + 47, + 36, + 33, + 39, + 44, + 46, + 41, + 45, + 30, + 42, + 37, + 40, + 38, + 26, + 34, + 44, + 50, + 36, + 35, + 37, + 41, + 50, + 46, + 44, + 45, + 45, + 45, + 45, + 44, + 26, + 46, + 35, + 34, + 40, + 41, + 46, + 43, + 50, + 46, + 44, + 45, + 27, + 40, + 29, + 45, + 35, + 46, + 41, + 40, + 42, + 49, + 41, + 36, + 40, + 47, + 46, + 47, + 39, + 34, + 43, + 34, + 29, + 29, + 42, + 35, + 45, + 34, + 41, + 44, + 45, + 38, + 46, + 31, + 41, + 44, + 32, + 29, + 33, + 43, + 29, + 43, + 47, + 46, + 26, + 49, + 30, + 45, + 46, + 44, + 33, + 50, + 29, + 45, + 40, + 28, + 41, + 46, + 25, + 47, + 46, + 28, + 27, + 42, + 49, + 35, + 38, + 36, + 35, + 41, + 44, + 45, + 40, + 42, + 45, + 45, + 45, + 44, + 32, + 39, + 27, + 41, + 42, + 41, + 46, + 44, + 41, + 35, + 41, + 45, + 40, + 42, + 43, + 40, + 44, + 49, + 42, + 50, + 28, + 35, + 45, + 49, + 41, + 41, + 40, + 39, + 46, + 46, + 45, + 46, + 38, + 45, + 40, + 41, + 41, + 50, + 25, + 47, + 41, + 27, + 44, + 46, + 34, + 26, + 46, + 43, + 40, + 46, + 39, + 46, + 27, + 39, + 35, + 46, + 27, + 44, + 27, + 42, + 46, + 46, + 43, + 46, + 40, + 39, + 46, + 33, + 42, + 35, + 46, + 28, + 46, + 45, + 53, + 36, + 42, + 53, + 27, + 31, + 41, + 41, + 37, + 30, + 45, + 45, + 34, + 31, + 44, + 38, + 41, + 38, + 31, + 35, + 39, + 35, + 39, + 46, + 45, + 46, + 36, + 36, + 50, + 30, + 34, + 41, + 46, + 35, + 40, + 39, + 28, + 45, + 38, + 35, + 38, + 28, + 46, + 41, + 36, + 37, + 30, + 28, + 35, + 30, + 45, + 46, + 46, + 25, + 29, + 43, + 47, + 46, + 45, + 46, + 40, + 35, + 46, + 45, + 43, + 45, + 45, + 46, + 33, + 37, + 33, + 42, + 46, + 40, + 30, + 45, + 33, + 33, + 32, + 31, + 34, + 34, + 45, + 37, + 39, + 33, + 50, + 41, + 27, + 40, + 47, + 40, + 46, + 45, + 31, + 43, + 29, + 41, + 47, + 42, + 30, + 43, + 34, + 45, + 36, + 39, + 46, + 37, + 28, + 45, + 42, + 47, + 43, + 32, + 42, + 40, + 27, + 37, + 30, + 47, + 39, + 33, + 42, + 46, + 40, + 39, + 41, + 46, + 39, + 38, + 49, + 39, + 40, + 31, + 30, + 41, + 49, + 44, + 42, + 46, + 46, + 46, + 29, + 45, + 42, + 40, + 39, + 53, + 46, + 40, + 33, + 42, + 46, + 45, + 33, + 34, + 46, + 41, + 35, + 42, + 30, + 45, + 40, + 34, + 46, + 46, + 46, + 49, + 49, + 42, + 41, + 44, + 37, + 49, + 36, + 44, + 35, + 41, + 39, + 39, + 42, + 46, + 40, + 33, + 30, + 40, + 35, + 47, + 45, + 44, + 47, + 32, + 33, + 42, + 45, + 33, + 41, + 40, + 27, + 28, + 50, + 45, + 46, + 43, + 46, + 25, + 45, + 33, + 36, + 39, + 47, + 40, + 40, + 50, + 34, + 43, + 41, + 40, + 46, + 34, + 35, + 47, + 45, + 32, + 45, + 40, + 29, + 34, + 45, + 34, + 33, + 44, + 46, + 31, + 41, + 42, + 31, + 28, + 27, + 38, + 33, + 46, + 41, + 34, + 46, + 35, + 44, + 46, + 41, + 47, + 50, + 41, + 47, + 31, + 31, + 34, + 48, + 35, + 46, + 33, + 44, + 28, + 31, + 28, + 30, + 29, + 40, + 42, + 42, + 47, + 40, + 42, + 49, + 42, + 29, + 39, + 28, + 46, + 41, + 33, + 43, + 39, + 41, + 40, + 45, + 45, + 35, + 47, + 45, + 42, + 26, + 40, + 34, + 34, + 35, + 34, + 46, + 35, + 29, + 38, + 40, + 31, + 41, + 46, + 31, + 35, + 35, + 31, + 39, + 40, + 46, + 27, + 33, + 39, + 41, + 29, + 30, + 46, + 43, + 35, + 49, + 32, + 49, + 46, + 29, + 39, + 47, + 44, + 30, + 40, + 30, + 31, + 35, + 47, + 46, + 44, + 40, + 35, + 28, + 41, + 38, + 29, + 50, + 34, + 46, + 28, + 35, + 39, + 37, + 46, + 28, + 43, + 37, + 36, + 40, + 31, + 41, + 40, + 32, + 31, + 42, + 36, + 33, + 30, + 41, + 28, + 37, + 45, + 31, + 38, + 40, + 35, + 44, + 37, + 41, + 46, + 32, + 39, + 43, + 29, + 29, + 40, + 45, + 34, + 40, + 40, + 41, + 45, + 45, + 36, + 31, + 41, + 43, + 41, + 29, + 27, + 31, + 27, + 41, + 39, + 39, + 46, + 35, + 47, + 47, + 28, + 46, + 40, + 35, + 33, + 40, + 44, + 45, + 45, + 29, + 45, + 41, + 26, + 39, + 35, + 29, + 43, + 49, + 33, + 32, + 43, + 45, + 29, + 37, + 37, + 42, + 27, + 44, + 41, + 40, + 39, + 36, + 48, + 33, + 46, + 27, + 44, + 28, + 34, + 46, + 38, + 44, + 34, + 40, + 40, + 43, + 42, + 34, + 28, + 45, + 39, + 47, + 45, + 45, + 34, + 46, + 45, + 34, + 35, + 43, + 34, + 32, + 46, + 39, + 46, + 35, + 27, + 29, + 40, + 39, + 42, + 44, + 45, + 45, + 41, + 38, + 31, + 42, + 46, + 48, + 29, + 33, + 36, + 37, + 35, + 45, + 46, + 39, + 30, + 39, + 30, + 30, + 45, + 46, + 41, + 29, + 33, + 45, + 33, + 30, + 39, + 34, + 45, + 27, + 27, + 45, + 40, + 49, + 40, + 46, + 41, + 28, + 30, + 42, + 28, + 41, + 30, + 40, + 50, + 39, + 41, + 31, + 27, + 40, + 45, + 33, + 45, + 46, + 43, + 39, + 38, + 32, + 44, + 26, + 45, + 41, + 41, + 41, + 38, + 31, + 31, + 29, + 27, + 37, + 41, + 38, + 35, + 30, + 45, + 45, + 46, + 28, + 32, + 42, + 27, + 46, + 31, + 33, + 41, + 45, + 31, + 44, + 30, + 45, + 38, + 41, + 45, + 40, + 39, + 35, + 39, + 45, + 46, + 34, + 32, + 33, + 28, + 34, + 46, + 40, + 30, + 42, + 44, + 41, + 41, + 36, + 36, + 45, + 28, + 32, + 46, + 40, + 50, + 43, + 45, + 25, + 27, + 41, + 46, + 29, + 28, + 46, + 29, + 45, + 41, + 46, + 44, + 29, + 41, + 40, + 45, + 41, + 36, + 40, + 32, + 45, + 45, + 32, + 36, + 41, + 44, + 34, + 31, + 41, + 31, + 40, + 37, + 45, + 46, + 31, + 29, + 40, + 31, + 40, + 40, + 35, + 44, + 44, + 28, + 28, + 45, + 43, + 30, + 39, + 30, + 44, + 41, + 42, + 44, + 27, + 45, + 41, + 46, + 31, + 31, + 31, + 32, + 39, + 30, + 33, + 31, + 36, + 45, + 41, + 45, + 45, + 46, + 35, + 41, + 41, + 36, + 30, + 42, + 29, + 45, + 46, + 50, + 42, + 40, + 39, + 45, + 35, + 40, + 30, + 41, + 46, + 39, + 41, + 42, + 30, + 43, + 35, + 27, + 45, + 35, + 31, + 46, + 44, + 33, + 35, + 42, + 28, + 33, + 47, + 45, + 37, + 33, + 46, + 41, + 46, + 28, + 37, + 30, + 39, + 43, + 42, + 45, + 41, + 34, + 42, + 39, + 33, + 46, + 38, + 30, + 41, + 27, + 47, + 40, + 31, + 49, + 41, + 42, + 37, + 39, + 46, + 33, + 42, + 38, + 41, + 46, + 30, + 35, + 33, + 43, + 33, + 46, + 27, + 27, + 33, + 29, + 45, + 40, + 44, + 41, + 46, + 37, + 46, + 39, + 34, + 34, + 46, + 27, + 40, + 34, + 50, + 45, + 45, + 45, + 35, + 35, + 32, + 40, + 40, + 45, + 43, + 35, + 40, + 39, + 31, + 36, + 44, + 39, + 39, + 50, + 39, + 49, + 47, + 28, + 45, + 45, + 46, + 41, + 44, + 33, + 45, + 32, + 36, + 28, + 47, + 35, + 38, + 42, + 40, + 40, + 39, + 33, + 33, + 32, + 30, + 46, + 34, + 46, + 29, + 40, + 34, + 45, + 30, + 27, + 36, + 28, + 42, + 41, + 44, + 31, + 27, + 38, + 29, + 46, + 46, + 40, + 34, + 45, + 40, + 41, + 28, + 40, + 42, + 46, + 47, + 31, + 46, + 46, + 34, + 46, + 41, + 39, + 46, + 37, + 46, + 36, + 31, + 40, + 46, + 37, + 29, + 42, + 35, + 39, + 42, + 37, + 44, + 45, + 44, + 33, + 35, + 46, + 40, + 45, + 33, + 40, + 33, + 35, + 43, + 45, + 41, + 41, + 44, + 40, + 45, + 26, + 46, + 29, + 39, + 46, + 45, + 41, + 34, + 30, + 27, + 50, + 31, + 45, + 39, + 34, + 49, + 45, + 40, + 40, + 43, + 40, + 50, + 38, + 46, + 40, + 37, + 34, + 40, + 31, + 41, + 35, + 42, + 44, + 31, + 45, + 42, + 40, + 41, + 38, + 33, + 47, + 45, + 46, + 44, + 31, + 39, + 46, + 38, + 27, + 46, + 30, + 28, + 49, + 34, + 46, + 36, + 42, + 39, + 36, + 45, + 45, + 34, + 33, + 30, + 53, + 45, + 46, + 36, + 41, + 45, + 46, + 45, + 45, + 29, + 42, + 39, + 36, + 33, + 45, + 41, + 45, + 40, + 42, + 35, + 37, + 33, + 29, + 30, + 35, + 35, + 45, + 35, + 41, + 41, + 45, + 44, + 28, + 34, + 44, + 42, + 42, + 45, + 40, + 39, + 43, + 43, + 33, + 30, + 29, + 31, + 42, + 39, + 27, + 26, + 46, + 27, + 39, + 27, + 31, + 34, + 31, + 34, + 31, + 28, + 37, + 28, + 49, + 35, + 39, + 34, + 39, + 46, + 36, + 35, + 39, + 42, + 42, + 40, + 33, + 34, + 32, + 33, + 44, + 37, + 42, + 50, + 41, + 39, + 31, + 46, + 40, + 33, + 36, + 34, + 43, + 40, + 31, + 33, + 37, + 35, + 41, + 44, + 33, + 42, + 47, + 46, + 37, + 35, + 28, + 39, + 32, + 47, + 35, + 40, + 31, + 33, + 39, + 50, + 29, + 27, + 27, + 40, + 29, + 42, + 32, + 31, + 50, + 44, + 39, + 27, + 43, + 50, + 40, + 31, + 40, + 46, + 35, + 39, + 35, + 30, + 36, + 40, + 30, + 45, + 34, + 43, + 46, + 44, + 38, + 45, + 43, + 30, + 40, + 46, + 33, + 31, + 40, + 35, + 35, + 33, + 41, + 34, + 50, + 39, + 40, + 32, + 46, + 41, + 34, + 34, + 46, + 43, + 39, + 33, + 42, + 32, + 36, + 29, + 39, + 38, + 35, + 35, + 32, + 45, + 46, + 46, + 32, + 46, + 31, + 32, + 34, + 49, + 42, + 47, + 27, + 39, + 38, + 46, + 41, + 41, + 30, + 39, + 45, + 46, + 28, + 38, + 31, + 34, + 37, + 28, + 41, + 46, + 34, + 39, + 40, + 46, + 33, + 30, + 34, + 28, + 34, + 30, + 34, + 46, + 34, + 45, + 32, + 41, + 38, + 46, + 43, + 29, + 46, + 31, + 45, + 32, + 27, + 39, + 38, + 31, + 41, + 46, + 38, + 45, + 28, + 32, + 41, + 37, + 32, + 34, + 34, + 28, + 47, + 39, + 34, + 29, + 30, + 40, + 46, + 47, + 47, + 38, + 26, + 39, + 40, + 35, + 37, + 35, + 34, + 47, + 35, + 46, + 38, + 27, + 40, + 46, + 40, + 29, + 46, + 26, + 49, + 34, + 42, + 44, + 27, + 31, + 44, + 31, + 50, + 45, + 40, + 40, + 40, + 39, + 45, + 36, + 45, + 50, + 27, + 45, + 39, + 40, + 43, + 29, + 45, + 36, + 39, + 27, + 31, + 46, + 33, + 27, + 41, + 34, + 36, + 33, + 45, + 31, + 28, + 40, + 45, + 40, + 29, + 30, + 45, + 47, + 50, + 34, + 36, + 40, + 45, + 27, + 35, + 41, + 40, + 34, + 33, + 34, + 45, + 30, + 45, + 37, + 34, + 28, + 39, + 50, + 34, + 34, + 32, + 31, + 34, + 45, + 50, + 43, + 30, + 47, + 46, + 34, + 41, + 45, + 39, + 46, + 40, + 33, + 31, + 42, + 35, + 35, + 41, + 35, + 42, + 45, + 34, + 45, + 32, + 26, + 41, + 33, + 41, + 50, + 32, + 42, + 46, + 30, + 33, + 33, + 40, + 41, + 42, + 47, + 28, + 32, + 46, + 42, + 42, + 38, + 41, + 29, + 34, + 42, + 44, + 42, + 40, + 36, + 41, + 30, + 35, + 31, + 41, + 28, + 47, + 33, + 53, + 43, + 46, + 28, + 31, + 33, + 42, + 41, + 31, + 46, + 46, + 28, + 34, + 36, + 29, + 45, + 33, + 30, + 40, + 40, + 30, + 32, + 50, + 31, + 30, + 41, + 47, + 34, + 36, + 47, + 39, + 47, + 40, + 33, + 33, + 40, + 32, + 36, + 35, + 39, + 35, + 29, + 47, + 30, + 35, + 27, + 40, + 35, + 25, + 34, + 40, + 37, + 31, + 43, + 45, + 45, + 37, + 40, + 44, + 41, + 35, + 30, + 45, + 46, + 41, + 46, + 45, + 40, + 29, + 39, + 45, + 44, + 44, + 45, + 28, + 33, + 44, + 40, + 27, + 45, + 27, + 31, + 35, + 50, + 32, + 39, + 42, + 46, + 46, + 45, + 41, + 37, + 45, + 28, + 34, + 33, + 30, + 39, + 33, + 31, + 35, + 29, + 46, + 41, + 45, + 42, + 36, + 45, + 42, + 46, + 41, + 35, + 44, + 45, + 29, + 50, + 36, + 30, + 28, + 33, + 32, + 36, + 30, + 45, + 40, + 34, + 40, + 31, + 33, + 37, + 40, + 46, + 29, + 46, + 28, + 40, + 29, + 28, + 40, + 43, + 41, + 33, + 27, + 37, + 50, + 45, + 41, + 45, + 34, + 41, + 39, + 34, + 32, + 33, + 45, + 42, + 27, + 53, + 42, + 32, + 39, + 38, + 41, + 28, + 35, + 37, + 35, + 46, + 44, + 27, + 34, + 31, + 33, + 37, + 31, + 45, + 40, + 47, + 49, + 40, + 46, + 33, + 38, + 40, + 37, + 38, + 43, + 35, + 40, + 46, + 38, + 43, + 49, + 33, + 31, + 50, + 30, + 32, + 42, + 41, + 47, + 45, + 36, + 36, + 36, + 40, + 28, + 46, + 29, + 34, + 30, + 27, + 39, + 39, + 40, + 41, + 35, + 45, + 37, + 40, + 32, + 45, + 29, + 40, + 46, + 34, + 37, + 40, + 45, + 42, + 35, + 33, + 31, + 34, + 35, + 34, + 50, + 35, + 41, + 40, + 34, + 40, + 47, + 45, + 31, + 46, + 42, + 36, + 41, + 40, + 53, + 34, + 45, + 28, + 30, + 28, + 40, + 46, + 34, + 34, + 29, + 36, + 30, + 47, + 38, + 34, + 50, + 46, + 42, + 45, + 36, + 37, + 36, + 46, + 40, + 39, + 32, + 40, + 46, + 50, + 36, + 33, + 32, + 50, + 29, + 39, + 33, + 41, + 31, + 31, + 28, + 39, + 33, + 35, + 42, + 40, + 40, + 31, + 26, + 31, + 47, + 35, + 41, + 38, + 30, + 40, + 33, + 34, + 32, + 34, + 33, + 40, + 46, + 35, + 36, + 46, + 45, + 39, + 37, + 33, + 45, + 32, + 42, + 39, + 31, + 46, + 46, + 31, + 46, + 31, + 35, + 42, + 40, + 46, + 39, + 36, + 35, + 28, + 31, + 45, + 33, + 33, + 47, + 45, + 39, + 45, + 41, + 35, + 34, + 43, + 36, + 33, + 27, + 42, + 28, + 32, + 45, + 38, + 50, + 27, + 35, + 31, + 35, + 28, + 34, + 28, + 47, + 46, + 46, + 53, + 49, + 42, + 37, + 52, + 35, + 34, + 34, + 50, + 33, + 40, + 30, + 33, + 50, + 46, + 32, + 30, + 43, + 33, + 39, + 36, + 36, + 39, + 28, + 49, + 42, + 39, + 34, + 45, + 34, + 44, + 27, + 41, + 38, + 40, + 43, + 35, + 45, + 38, + 46, + 39, + 39, + 39, + 45, + 46, + 37, + 40, + 46, + 35, + 39, + 29, + 40, + 41, + 49, + 35, + 41, + 27, + 36, + 28, + 42, + 34, + 28, + 35, + 33, + 40, + 39, + 27, + 47, + 41, + 31, + 43, + 46, + 26, + 31, + 43, + 33, + 41, + 39, + 28, + 41, + 45, + 45, + 28, + 39, + 31, + 26, + 40, + 31, + 35, + 37, + 36, + 34, + 50, + 31, + 44, + 28, + 26, + 28, + 26, + 39, + 33, + 41, + 45, + 31, + 33, + 32, + 41, + 40, + 40, + 41, + 39, + 34, + 45, + 35, + 28, + 31, + 31, + 29, + 39, + 27, + 31, + 45, + 46, + 43, + 39, + 33, + 46, + 38, + 41, + 34, + 34, + 40, + 40, + 49, + 40, + 45, + 46, + 49, + 36, + 29, + 33, + 46, + 30, + 27, + 41, + 32, + 32, + 31, + 46, + 34, + 28, + 31, + 27, + 36, + 38, + 38, + 36, + 34, + 46, + 46, + 33, + 37, + 44, + 40, + 46, + 41, + 33, + 46, + 36, + 40, + 31, + 31, + 27, + 50, + 45, + 46, + 39, + 46, + 46, + 31, + 45, + 32, + 34, + 33, + 42, + 38, + 39, + 31, + 41, + 40, + 29, + 53, + 46, + 45, + 45, + 32, + 43, + 32, + 35, + 41, + 45, + 44, + 33, + 53, + 34, + 46, + 35, + 35, + 32, + 32, + 50, + 50, + 26, + 45, + 40, + 30, + 45, + 45, + 27, + 34, + 37, + 29, + 34, + 36, + 26, + 42, + 45, + 31, + 46, + 39, + 45, + 46, + 50, + 34, + 40, + 28, + 27, + 39, + 40, + 28, + 30, + 39, + 29, + 40, + 39, + 33, + 29, + 45, + 32, + 40, + 36, + 33, + 45, + 41, + 27, + 35, + 34, + 41, + 38, + 28, + 50, + 33, + 34, + 39, + 35, + 39, + 50, + 33, + 41, + 46, + 45, + 34, + 33, + 36, + 46, + 31, + 28, + 36, + 42, + 34, + 45, + 33, + 30, + 47, + 45, + 34, + 25, + 33, + 40, + 42, + 33, + 44, + 40, + 36, + 45, + 53, + 40, + 27, + 31, + 40, + 40, + 40, + 46, + 32, + 37, + 29, + 46, + 35, + 35, + 32, + 43, + 40, + 27, + 43, + 40, + 30, + 40, + 45, + 41, + 41, + 34, + 33, + 46, + 44, + 46, + 41, + 49, + 39, + 31, + 47, + 39, + 37, + 34, + 46, + 40, + 37, + 36, + 40, + 37, + 39, + 33, + 39, + 46, + 37, + 46, + 37, + 39, + 34, + 42, + 46, + 50, + 41, + 29, + 34, + 29, + 30, + 41, + 45, + 33, + 33, + 33, + 34, + 28, + 44, + 34, + 37, + 36, + 49, + 28, + 29, + 40, + 34, + 40, + 50, + 32, + 41, + 34, + 45, + 34, + 39, + 40, + 46, + 33, + 41, + 33, + 46, + 31, + 38, + 38, + 45, + 36, + 30, + 42, + 46, + 32, + 40, + 37, + 44, + 36, + 43, + 28, + 35, + 33, + 29, + 27, + 40, + 27, + 28, + 50, + 35, + 45, + 46, + 32, + 44, + 37, + 42, + 41, + 45, + 33, + 53, + 34, + 36, + 41, + 31, + 29, + 31, + 32, + 38, + 41, + 31, + 45, + 41, + 50, + 35, + 44, + 32, + 34, + 36, + 37, + 32, + 34, + 33, + 33, + 32, + 39, + 37, + 40, + 28, + 40, + 26, + 29, + 39, + 37, + 33, + 36, + 49, + 41, + 41, + 46, + 32, + 45, + 44, + 30, + 36, + 33, + 46, + 35, + 43, + 32, + 53, + 49, + 40, + 50, + 35, + 50, + 27, + 28, + 31, + 33, + 30, + 45, + 45, + 40, + 31, + 46, + 46, + 30, + 46, + 34, + 39, + 39, + 33, + 42, + 40, + 34, + 32, + 44, + 31, + 46, + 27, + 37, + 36, + 35, + 45, + 28, + 41, + 31, + 40, + 45, + 42, + 41, + 39, + 33, + 28, + 29, + 35, + 34, + 33, + 27, + 45, + 36, + 32, + 50, + 27, + 32, + 32, + 28, + 44, + 47, + 29, + 33, + 40, + 41, + 41, + 39, + 36, + 41, + 32, + 33, + 45, + 40, + 44, + 38, + 28, + 39, + 28, + 37, + 31, + 34, + 29, + 38, + 46, + 35, + 32, + 34, + 34, + 33, + 35, + 36, + 45, + 50, + 53, + 31, + 38, + 44, + 36, + 33, + 34, + 35, + 45, + 40, + 41, + 35, + 32, + 32, + 28, + 33, + 34, + 46, + 33, + 34, + 44, + 28, + 41, + 27, + 28, + 34, + 39, + 53, + 35, + 40, + 27, + 40, + 34, + 35, + 35, + 32, + 33, + 45, + 32, + 34, + 36, + 36, + 30, + 34, + 45, + 33, + 45, + 35, + 46, + 45, + 28, + 34, + 39, + 34, + 43, + 44, + 32, + 43, + 41, + 29, + 32, + 33, + 46, + 30, + 35, + 46, + 34, + 33, + 31, + 28, + 27, + 30, + 30, + 36, + 46, + 32, + 45, + 39, + 40, + 49, + 49, + 38, + 34, + 32, + 46, + 34, + 40, + 39, + 35, + 28, + 34, + 33, + 33, + 41, + 31, + 32, + 36, + 33, + 33, + 45, + 37, + 37, + 29, + 40, + 30, + 29, + 45, + 39, + 31, + 36, + 45, + 43, + 35, + 35, + 35, + 36, + 52, + 31, + 32, + 36, + 38, + 40, + 36, + 41, + 30, + 45, + 33, + 38, + 46, + 45, + 34, + 31, + 36, + 40, + 39, + 37, + 38, + 33, + 31, + 38, + 49, + 34, + 27, + 33, + 35, + 33, + 50, + 41, + 33, + 43, + 40, + 46, + 38, + 47, + 45, + 29, + 45, + 40, + 35, + 35, + 44, + 37, + 45, + 34, + 45, + 40, + 45, + 32, + 36, + 41, + 40, + 35, + 33, + 49, + 44, + 29, + 31, + 34, + 32, + 33, + 40, + 35, + 30, + 35, + 47, + 35, + 45, + 28, + 41, + 47, + 46, + 34, + 33, + 38, + 30, + 39, + 46, + 50, + 41, + 40, + 32, + 29, + 29, + 34, + 32, + 38, + 41, + 33, + 35, + 29, + 34, + 46, + 27, + 32, + 35, + 31, + 46, + 35, + 33, + 26, + 40, + 45, + 38, + 45, + 43, + 27, + 31, + 26, + 31, + 50, + 30, + 35, + 33, + 46, + 40, + 44, + 47, + 32, + 39, + 35, + 31, + 29, + 39, + 35, + 29, + 38, + 43, + 36, + 29, + 40, + 42, + 35, + 32, + 40, + 41, + 38, + 40, + 34, + 34, + 27, + 36, + 34, + 49, + 42, + 41, + 27, + 50, + 41, + 32, + 33, + 30, + 34, + 45, + 50, + 30, + 34, + 31, + 39, + 49, + 27, + 32, + 27, + 34, + 27, + 33, + 36, + 46, + 30, + 28, + 50, + 50, + 44, + 34, + 37, + 37, + 37, + 36, + 27, + 33, + 33, + 36, + 38, + 33, + 39, + 32, + 27, + 31, + 32, + 27, + 43, + 35, + 28, + 32, + 34, + 32, + 33, + 35, + 50, + 28, + 34, + 39, + 34, + 39, + 36, + 41, + 27, + 26, + 46, + 46, + 42, + 27, + 36, + 39, + 35, + 34, + 41, + 31, + 33, + 36, + 39, + 50, + 38, + 38, + 39, + 31, + 26, + 52, + 46, + 41, + 43, + 46, + 27, + 39, + 50, + 33, + 34, + 50, + 31, + 36, + 45, + 34, + 41, + 38, + 50, + 27, + 31, + 41, + 34, + 33, + 40, + 31, + 39, + 36, + 31, + 45, + 34, + 49, + 45, + 35, + 39, + 30, + 27, + 40, + 45, + 45, + 36, + 39, + 43, + 40, + 40, + 39, + 28, + 36, + 37, + 46, + 30, + 35, + 34, + 32, + 35, + 32, + 40, + 31, + 45, + 40, + 36, + 36, + 36, + 37, + 28, + 31, + 46, + 31, + 41, + 41, + 45, + 32, + 34, + 30, + 36, + 39, + 45, + 36, + 37, + 29, + 41, + 50, + 35, + 34, + 35, + 33, + 29, + 45, + 40, + 33, + 36, + 32, + 33, + 50, + 36, + 36, + 46, + 39, + 37, + 36, + 45, + 46, + 39, + 46, + 28, + 27, + 34, + 36, + 42, + 32, + 46, + 44, + 39, + 31, + 45, + 50, + 37, + 36, + 33, + 39, + 35, + 32, + 35, + 39, + 47, + 35, + 32, + 33, + 45, + 28, + 33, + 40, + 33, + 31, + 50, + 29, + 33, + 32, + 32, + 53, + 42, + 31, + 27, + 31, + 46, + 28, + 33, + 31, + 36, + 34, + 32, + 46, + 37, + 45, + 32, + 34, + 33, + 32, + 50, + 33, + 46, + 28, + 35, + 45, + 33, + 29, + 45, + 41, + 50, + 37, + 45, + 39, + 45, + 49, + 45, + 30, + 39, + 47, + 45, + 33, + 34, + 29, + 34, + 33, + 29, + 33, + 34, + 46, + 39, + 27, + 27, + 35, + 38, + 46, + 41, + 33, + 51, + 41, + 28, + 32, + 41, + 45, + 34, + 50, + 33, + 42, + 36, + 45, + 40, + 34, + 31, + 35, + 32, + 31, + 40, + 27, + 49, + 50, + 40, + 35, + 31, + 45, + 36, + 40, + 32, + 36, + 37, + 50, + 45, + 34, + 29, + 50, + 31, + 41, + 34, + 36, + 33, + 36, + 32, + 33, + 34, + 45, + 37, + 31, + 31, + 31, + 32, + 42, + 32, + 50, + 31, + 32, + 39, + 28, + 38, + 39, + 37, + 31, + 45, + 33, + 30, + 39, + 41, + 38, + 29, + 39, + 39, + 31, + 30, + 37, + 34, + 29, + 28, + 32, + 29, + 36, + 33, + 45, + 32, + 42, + 32, + 42, + 32, + 36, + 28, + 28, + 34, + 40, + 38, + 45, + 35, + 34, + 40, + 27, + 34, + 35, + 34, + 45, + 34, + 26, + 52, + 46, + 45, + 47, + 53, + 42, + 35, + 33, + 36, + 32, + 34, + 47, + 39, + 41, + 32, + 34, + 36, + 50, + 36, + 35, + 45, + 32, + 32, + 39, + 37, + 33, + 38, + 50, + 50, + 33, + 45, + 35, + 33, + 33, + 31, + 31, + 46, + 31, + 35, + 52, + 31, + 52, + 40, + 36, + 27, + 32, + 31, + 28, + 37, + 37, + 35, + 50, + 45, + 33, + 35, + 39, + 35, + 33, + 34, + 34, + 38, + 45, + 33, + 46, + 33, + 35, + 32, + 36, + 41, + 34, + 31, + 45, + 38, + 34, + 37, + 35, + 35, + 34, + 45, + 36, + 27, + 30, + 35, + 35, + 35, + 45, + 33, + 36, + 39, + 35, + 27, + 41, + 37, + 33, + 32, + 45, + 35, + 39, + 36, + 50, + 31, + 32, + 35, + 34, + 27, + 31, + 34, + 46, + 32, + 32, + 50, + 32, + 32, + 43, + 33, + 31, + 31, + 30, + 33, + 27, + 32, + 37, + 29, + 37, + 34, + 38, + 39, + 27, + 32, + 35, + 31, + 36, + 33, + 30, + 50, + 36, + 27, + 33, + 32, + 47, + 44, + 35, + 34, + 35, + 45, + 30, + 38, + 27, + 29, + 37, + 46, + 38, + 31, + 45, + 36, + 36, + 47, + 41, + 36, + 52, + 34, + 40, + 35, + 27, + 37, + 37, + 32, + 38, + 39, + 45, + 34, + 37, + 28, + 38, + 40, + 27, + 29, + 38, + 27, + 42, + 26, + 34, + 33, + 46, + 39, + 36, + 50, + 32, + 28, + 28, + 33, + 34, + 33, + 45, + 33, + 39, + 31, + 31, + 40, + 31, + 34, + 44, + 31, + 27, + 31, + 37, + 30, + 45, + 34, + 35, + 31, + 45, + 40, + 30, + 31, + 34, + 33, + 34, + 40, + 35, + 39, + 50, + 37, + 45, + 32, + 35, + 34, + 32, + 31, + 32, + 38, + 39, + 39, + 28, + 41, + 30, + 45, + 32, + 39, + 41, + 39, + 38, + 32, + 37, + 45, + 41, + 49, + 35, + 45, + 45, + 35, + 36, + 45, + 30, + 46, + 51, + 42, + 35, + 34, + 33, + 41, + 32, + 30, + 32, + 40, + 42, + 31, + 44, + 27, + 32, + 40, + 31, + 32, + 50, + 41, + 41, + 39, + 38, + 33, + 46, + 35, + 28, + 28, + 33, + 33, + 33, + 34, + 39, + 32, + 33, + 34, + 35, + 39, + 46, + 32, + 40, + 31, + 32, + 37, + 40, + 33, + 32, + 31, + 33, + 50, + 34, + 31, + 35, + 33, + 27, + 37, + 36, + 35, + 52, + 34, + 37, + 34, + 45, + 52, + 34, + 32, + 32, + 44, + 31, + 44, + 28, + 39, + 45, + 39, + 36, + 32, + 36, + 25, + 35, + 35, + 33, + 38, + 27, + 30, + 36, + 34, + 33, + 39, + 32, + 50, + 36, + 35, + 27, + 32, + 28, + 35, + 44, + 45, + 50, + 32, + 39, + 35, + 36, + 36, + 39, + 36, + 46, + 46, + 28, + 46, + 41, + 31, + 32, + 33, + 32, + 33, + 34, + 41, + 33, + 44, + 45, + 38, + 27, + 34, + 31, + 50, + 35, + 36, + 34, + 32, + 28, + 37, + 50, + 46, + 34, + 30, + 33, + 33, + 50, + 33, + 33, + 36, + 28, + 33, + 33, + 27, + 39, + 32, + 37, + 32, + 32, + 38, + 34, + 31, + 35, + 33, + 46, + 50, + 41, + 38, + 37, + 51, + 50, + 35, + 35, + 33, + 37, + 36, + 34, + 39, + 39, + 36, + 35, + 32, + 38, + 39, + 33, + 32, + 51, + 34, + 50, + 28, + 34, + 47, + 50, + 38, + 41, + 29, + 27, + 29, + 32, + 46, + 38, + 51, + 33, + 27, + 46, + 33, + 35, + 35, + 33, + 39, + 31, + 31, + 46, + 35, + 33, + 37, + 31, + 36, + 39, + 50, + 52, + 45, + 40, + 36, + 35, + 50, + 36, + 40, + 34, + 35, + 31, + 41, + 50, + 32, + 30, + 35, + 41, + 31, + 34, + 42, + 30, + 34, + 38, + 31, + 46, + 43, + 29, + 36, + 51, + 33, + 50, + 36, + 32, + 45, + 41, + 52, + 36, + 46, + 30, + 50, + 32, + 33, + 36, + 46, + 28, + 39, + 32, + 35, + 39, + 50, + 32, + 32, + 36, + 33, + 27, + 45, + 31, + 44, + 35, + 40, + 51, + 35, + 31, + 36, + 50, + 41, + 36, + 28, + 46, + 27, + 33, + 31, + 33, + 39, + 41, + 36, + 41, + 50, + 31, + 39, + 34, + 26, + 27, + 35, + 31, + 38, + 33, + 39, + 32, + 50, + 34, + 33, + 50, + 50, + 34, + 33, + 39, + 35, + 32, + 35, + 39, + 33, + 31, + 36, + 45, + 40, + 31, + 33, + 35, + 35, + 37, + 45, + 33, + 34, + 39, + 36, + 33, + 45, + 34, + 31, + 30, + 40, + 31, + 45, + 39, + 50, + 35, + 31, + 27, + 34, + 51, + 50, + 35, + 31, + 33, + 40, + 34, + 42, + 31, + 39, + 37, + 36, + 31, + 31, + 31, + 32, + 34, + 31, + 30, + 36, + 32, + 34, + 35, + 34, + 36, + 34, + 29, + 33, + 34, + 32, + 32, + 46, + 39, + 34, + 33, + 50, + 35, + 33, + 27, + 34, + 39, + 32, + 26, + 31, + 39, + 45, + 28, + 51, + 39, + 32, + 33, + 36, + 36, + 36, + 31, + 51, + 35, + 51, + 28, + 36, + 37, + 33, + 34, + 37, + 31, + 33, + 40, + 33, + 33, + 39, + 46, + 45, + 39, + 45, + 32, + 30, + 26, + 35, + 45, + 31, + 37, + 31, + 31, + 32, + 33, + 26, + 29, + 44, + 33, + 35, + 33, + 34, + 34, + 34, + 27, + 37, + 50, + 33, + 35, + 28, + 33, + 34, + 31, + 32, + 34, + 39, + 42, + 37, + 45, + 35, + 33, + 37, + 35, + 33, + 31, + 49, + 33, + 33, + 32, + 33, + 34, + 42, + 30, + 51, + 32, + 46, + 36, + 41, + 34, + 45, + 29, + 37, + 31, + 30, + 36, + 32, + 33, + 32, + 45, + 32, + 45, + 46, + 47, + 40, + 31, + 31, + 45, + 33, + 31, + 50, + 36, + 33, + 36, + 37, + 31, + 50, + 35, + 31, + 32, + 35, + 37, + 43, + 34, + 50, + 42, + 36, + 45, + 50, + 37, + 32, + 29, + 32, + 34, + 34, + 31, + 34, + 32, + 36, + 31, + 32, + 45, + 45, + 35, + 32, + 30, + 38, + 32, + 36, + 34, + 51, + 39, + 34, + 50, + 34, + 50, + 31, + 34, + 32, + 50, + 36, + 33, + 32, + 50, + 30, + 34, + 35, + 52, + 44, + 39, + 31, + 33, + 31, + 28, + 34, + 34, + 27, + 35, + 32, + 31, + 31, + 45, + 37, + 33, + 35, + 32, + 45, + 38, + 51, + 34, + 34, + 31, + 33, + 50, + 36, + 34, + 39, + 35, + 32, + 50, + 33, + 30, + 31, + 34, + 28, + 36, + 32, + 33, + 31, + 50, + 32, + 36, + 45, + 34, + 50, + 32, + 33, + 32, + 38, + 35, + 51, + 34, + 33, + 44, + 36, + 32, + 36, + 31, + 33, + 34, + 39, + 39, + 32, + 41, + 39, + 50, + 34, + 32, + 33, + 51, + 33, + 28, + 32, + 32, + 50, + 34, + 37, + 32, + 32, + 33, + 39, + 36, + 36, + 33, + 32, + 50, + 32, + 50, + 34, + 31, + 33, + 32, + 39, + 40, + 34, + 45, + 32, + 33, + 50, + 50, + 35, + 32, + 39, + 31, + 36, + 41, + 41, + 36, + 32, + 31, + 32, + 33, + 52, + 50, + 35, + 30, + 34, + 52, + 32, + 39, + 41, + 45, + 36, + 31, + 32, + 25, + 50, + 34, + 51, + 31, + 36, + 45, + 34, + 45, + 33, + 33, + 34, + 34, + 32, + 36, + 32, + 33, + 31, + 50, + 31, + 42, + 37, + 37, + 30, + 30, + 32, + 50, + 49, + 35, + 31, + 32, + 51, + 32, + 29, + 33, + 34, + 32, + 34, + 34, + 32, + 32, + 41, + 36, + 36, + 45, + 31, + 36, + 30, + 37, + 34, + 35, + 37, + 31, + 36, + 33, + 35, + 36, + 38, + 45, + 34, + 42, + 45, + 45, + 31, + 27, + 32, + 33, + 32, + 33, + 50, + 37, + 32, + 39, + 51, + 31, + 34, + 39, + 32, + 27, + 35, + 35, + 37, + 33, + 50, + 31, + 42, + 33, + 36, + 32, + 33, + 27, + 31, + 51, + 33, + 34, + 39, + 34, + 32, + 32, + 45, + 39, + 33, + 31, + 50, + 32, + 27, + 47, + 31, + 27, + 39, + 51, + 34, + 35, + 34, + 31, + 37, + 38, + 31, + 30, + 32, + 39, + 31, + 50, + 34, + 35, + 31, + 33, + 32, + 32, + 32, + 39, + 32, + 36, + 31, + 51, + 39, + 32, + 31, + 33, + 32, + 27, + 33, + 50, + 31, + 33, + 51, + 50, + 27, + 31, + 51, + 36, + 51, + 32, + 34, + 39, + 39, + 31, + 33, + 38, + 36, + 34, + 27, + 50, + 32, + 39, + 33, + 51, + 34, + 36, + 50, + 35, + 50, + 36, + 33, + 36, + 33, + 36, + 36, + 51, + 46, + 33, + 32, + 37, + 34, + 31, + 34, + 27, + 31, + 31, + 33, + 45, + 41, + 34, + 45, + 33, + 31, + 34, + 33, + 30, + 32, + 33, + 35, + 51, + 34, + 37, + 35, + 45, + 32, + 46, + 33, + 42, + 35, + 31, + 33, + 50, + 37, + 28, + 27, + 39, + 32, + 36, + 27, + 50, + 36, + 32, + 27, + 39, + 32, + 27, + 36, + 35, + 39, + 34, + 34, + 35, + 32, + 32, + 41, + 39, + 28, + 36, + 36, + 26, + 32, + 33, + 51, + 39, + 52, + 51, + 34, + 50, + 36, + 33, + 34, + 33, + 31, + 32, + 31, + 36, + 32, + 34, + 32, + 45, + 32, + 35, + 26, + 37, + 35, + 34, + 32, + 50, + 33, + 31, + 32, + 31, + 31, + 32, + 34, + 31, + 27, + 50, + 31, + 32, + 33, + 50, + 45, + 34, + 37, + 32, + 37, + 37, + 45, + 39, + 36, + 45, + 50, + 32, + 50, + 34, + 30, + 31, + 34, + 32, + 34, + 51, + 39, + 52, + 39, + 32, + 52, + 32, + 28, + 28, + 28, + 31, + 37, + 50, + 27, + 27, + 33, + 32, + 27, + 31, + 50, + 36, + 28, + 32, + 39, + 31, + 45, + 27, + 36, + 32, + 50, + 34, + 36, + 33, + 31, + 33, + 33, + 50, + 38, + 33, + 50, + 32, + 31, + 34, + 32, + 39, + 33, + 32, + 37, + 30, + 50, + 35, + 33, + 35, + 35, + 46, + 26, + 41, + 50, + 34, + 38, + 45, + 28, + 50, + 28, + 34, + 50, + 34, + 51, + 36, + 35, + 52, + 36, + 33, + 39, + 32, + 50, + 36, + 31, + 52, + 35, + 41, + 28, + 46, + 35, + 38, + 44, + 34, + 33, + 50, + 35, + 31, + 32, + 30, + 32, + 31, + 34, + 34, + 34, + 31, + 51, + 28, + 31, + 39, + 39, + 35, + 39, + 32, + 32, + 30, + 34, + 36, + 35, + 32, + 33, + 50, + 49, + 50, + 35, + 32, + 28, + 32, + 33, + 51, + 46, + 32, + 27, + 39, + 41, + 31, + 37, + 51, + 27, + 44, + 34, + 33, + 34, + 32, + 44, + 31, + 33, + 32, + 34, + 31, + 50, + 34, + 39, + 50, + 50, + 45, + 31, + 46, + 31, + 52, + 50, + 35, + 31, + 31, + 37, + 37, + 38, + 37, + 50, + 37, + 33, + 32, + 31, + 33, + 34, + 34, + 31, + 35, + 34, + 34, + 31, + 34, + 34, + 52, + 31, + 36, + 32, + 36, + 45, + 33, + 45, + 31, + 36, + 32, + 32, + 33, + 39, + 51, + 32, + 30, + 34, + 52, + 45, + 34, + 34, + 37, + 31, + 34, + 32, + 50, + 50, + 35, + 30, + 31, + 36, + 39, + 50, + 33, + 32, + 34, + 33, + 34, + 50, + 33, + 27, + 34, + 51, + 37, + 52, + 45, + 33, + 32, + 51, + 34, + 50, + 31, + 32, + 51, + 33, + 34, + 36, + 50, + 35, + 52, + 37, + 33, + 36, + 31, + 51, + 33, + 27, + 28, + 50, + 52, + 33, + 31, + 31, + 33, + 37, + 49, + 32, + 32, + 36, + 39, + 45, + 32, + 32, + 32, + 32, + 31, + 34, + 34, + 32, + 28, + 27, + 50, + 34, + 45, + 30, + 34, + 33, + 36, + 33, + 52, + 37, + 31, + 33, + 30, + 39, + 36, + 31, + 41, + 36, + 42, + 35, + 37, + 37, + 32, + 36, + 32, + 31, + 31, + 31, + 36, + 35, + 34, + 50, + 33, + 33, + 31, + 40, + 36, + 36, + 27, + 37, + 37, + 30, + 33, + 32, + 31, + 35, + 27, + 32, + 51, + 34, + 32, + 32, + 30, + 52, + 33, + 32, + 32, + 33, + 33, + 33, + 27, + 32, + 36, + 28, + 45, + 33, + 31, + 34, + 33, + 33, + 33, + 52, + 31, + 51, + 33, + 52, + 33, + 35, + 41, + 26, + 32, + 36, + 50, + 36, + 31, + 34, + 34, + 32, + 33, + 37, + 35, + 52, + 39, + 31, + 34, + 36, + 36, + 31, + 32, + 32, + 42, + 27, + 50, + 34, + 33, + 32, + 34, + 32, + 32, + 37, + 31, + 32, + 51, + 50, + 33, + 51, + 30, + 31, + 34, + 33, + 34, + 31, + 30, + 32, + 32, + 31, + 36, + 34, + 37, + 33, + 45, + 31, + 32, + 39, + 36, + 36, + 32, + 31, + 33, + 32, + 32, + 50, + 32, + 31, + 33, + 51, + 34, + 50, + 33, + 32, + 32, + 32, + 31, + 45, + 39, + 32, + 32, + 52, + 52, + 45, + 50, + 34, + 35, + 32, + 52, + 39, + 32, + 31, + 33, + 36, + 36, + 32, + 52, + 34, + 36, + 32, + 32, + 34, + 50, + 33, + 31, + 26, + 30, + 36, + 32, + 39, + 32, + 33, + 31, + 31, + 36, + 51, + 32, + 33, + 51, + 41, + 32, + 52, + 33, + 51, + 45, + 32, + 52, + 51, + 31, + 50, + 32, + 52, + 39, + 31, + 34, + 30, + 32, + 31, + 33, + 36, + 36, + 32, + 51, + 32, + 32, + 34, + 33, + 52, + 36, + 50, + 34, + 36, + 34, + 34, + 31, + 32, + 33, + 31, + 33, + 32, + 46, + 32, + 36, + 36, + 32, + 30, + 36, + 33, + 34, + 34, + 32, + 52, + 37, + 32, + 50, + 35, + 32, + 50, + 35, + 31, + 32, + 51, + 50, + 31, + 52, + 31, + 52, + 32, + 50, + 30, + 31, + 27, + 32, + 32, + 39, + 31, + 34, + 31, + 32, + 32, + 36, + 31, + 32, + 33, + 32, + 50, + 33, + 50, + 32, + 33, + 31, + 31, + 32, + 35, + 33, + 32, + 39, + 39, + 34, + 50, + 32, + 34, + 30, + 31, + 34, + 32, + 32, + 34, + 34, + 35, + 36, + 33, + 34, + 34, + 36, + 26, + 36, + 33, + 36, + 30, + 39, + 28, + 35, + 51, + 51, + 52, + 32, + 36, + 27, + 36, + 51, + 32, + 45, + 31, + 50, + 32, + 39, + 51, + 32, + 32, + 33, + 34, + 31, + 31, + 51, + 51, + 31, + 39, + 34, + 52, + 32, + 52, + 32, + 32, + 33, + 32, + 36, + 32, + 36, + 31, + 32, + 34, + 50, + 39, + 33, + 30, + 31, + 31, + 35, + 34, + 27, + 31, + 36, + 32, + 34, + 39, + 33, + 51, + 36, + 31, + 35, + 37, + 36, + 33, + 32, + 33, + 31, + 36, + 34, + 36, + 50, + 32, + 32, + 32, + 35, + 32, + 33, + 26, + 50, + 33, + 51, + 26, + 33, + 51, + 36, + 32, + 34, + 39, + 52, + 36, + 34, + 26, + 32, + 39, + 39, + 27, + 32, + 51, + 35, + 32, + 35, + 32, + 33, + 34, + 52, + 33, + 37, + 33, + 34, + 41, + 52, + 33, + 33, + 36, + 52, + 32, + 31, + 35, + 31, + 32, + 33, + 39, + 34, + 32, + 37, + 31, + 33, + 39, + 31, + 33, + 32, + 37, + 52, + 32, + 52, + 27, + 36, + 36, + 32, + 32, + 52, + 37, + 39, + 52, + 37, + 32, + 31, + 31, + 35, + 32, + 36, + 32, + 35, + 33, + 36, + 32, + 52, + 34, + 32, + 51, + 51, + 32, + 32, + 51, + 32, + 51, + 52, + 31, + 32, + 32, + 36, + 51, + 33, + 26, + 35, + 51, + 52, + 32, + 31, + 31, + 36, + 32, + 32, + 34, + 32, + 52, + 31, + 32, + 35, + 32, + 35, + 31, + 52, + 35, + 33, + 33, + 36, + 32, + 32, + 32, + 33, + 32, + 32, + 34, + 51, + 34, + 36, + 32, + 33, + 52, + 32, + 32, + 32, + 33, + 32, + 33, + 31, + 32, + 33, + 32, + 36, + 36, + 39, + 36, + 36, + 33, + 31, + 30, + 28, + 52, + 32, + 36, + 37, + 32, + 33, + 51, + 32, + 32, + 31, + 32, + 36, + 32, + 37, + 27, + 31, + 32, + 31, + 51, + 28, + 28, + 31, + 36, + 33, + 51, + 34, + 35, + 31, + 32, + 52, + 32, + 28, + 32, + 36, + 32, + 32, + 36, + 37, + 32, + 36, + 33, + 51, + 36, + 36, + 36, + 31, + 51, + 32, + 32, + 32, + 51, + 32, + 36, + 32, + 32, + 32, + 32, + 32, + 36, + 52, + 33, + 27, + 33, + 34, + 39, + 51, + 32, + 33, + 33, + 27, + 34, + 36, + 28, + 32, + 32, + 32, + 45, + 51, + 26, + 33, + 30, + 32, + 51, + 36, + 30, + 33, + 36, + 32, + 32, + 32, + 32, + 37, + 36, + 34, + 36, + 36, + 33, + 32, + 33, + 32, + 32, + 51, + 28, + 32, + 32, + 33, + 36, + 31, + 30, + 32, + 36, + 32, + 51, + 51, + 36, + 27, + 36, + 32, + 31, + 32, + 32, + 27, + 36, + 32, + 32, + 32, + 33, + 27, + 34, + 36, + 32, + 52, + 32, + 51, + 34, + 31, + 32, + 32, + 36, + 32, + 32, + 33, + 33, + 32, + 33, + 41, + 52, + 32, + 27, + 34, + 30, + 34, + 41, + 32, + 34, + 36, + 36, + 36, + 32, + 32, + 31, + 36, + 36, + 32, + 33, + 31, + 33, + 32, + 32, + 51, + 31, + 32, + 31, + 32, + 32, + 31, + 31, + 34, + 36, + 52, + 31, + 33, + 39, + 30, + 34, + 31, + 32, + 32, + 32, + 33, + 32, + 33, + 31, + 31, + 39, + 30, + 32, + 35, + 36, + 32, + 26, + 32, + 30, + 31, + 39, + 32, + 39, + 31, + 36, + 32, + 39, + 32, + 32, + 51, + 32, + 32, + 32, + 33, + 36, + 36, + 32, + 35, + 33, + 51, + 37, + 31, + 51, + 33, + 52, + 32, + 36, + 31, + 52, + 32, + 36, + 32, + 51, + 37, + 32, + 36, + 32, + 32, + 26, + 32, + 30, + 36, + 27, + 32, + 30, + 51, + 39, + 30, + 32, + 36, + 32, + 33, + 32, + 33, + 36, + 32, + 36, + 33, + 36, + 36, + 36, + 32, + 51, + 51, + 33, + 33, + 51, + 32, + 31, + 32, + 36, + 36, + 33, + 34, + 51, + 32, + 30, + 32, + 33, + 31, + 31, + 32, + 33, + 33, + 36, + 32, + 32, + 51, + 28, + 32, + 36, + 39, + 52, + 32, + 36, + 32, + 39, + 32, + 32, + 33, + 32, + 32, + 51, + 51, + 33, + 37, + 32, + 37, + 34, + 36, + 32, + 36, + 32, + 32, + 34, + 31, + 32, + 32, + 32, + 35, + 36, + 32, + 37, + 37, + 51, + 32, + 32, + 32, + 32, + 26, + 31, + 31, + 31, + 34, + 32, + 33, + 32, + 37, + 32, + 32, + 36, + 36, + 51, + 32, + 36, + 37, + 39, + 33, + 32, + 36, + 32, + 26, + 32, + 32, + 32, + 36, + 32, + 32, + 34, + 39, + 34, + 39, + 32, + 27, + 35, + 30, + 32, + 32, + 39, + 32, + 32, + 37, + 32, + 32, + 31, + 32, + 36, + 31, + 37, + 32, + 32, + 31, + 34, + 34, + 30, + 32, + 32, + 36, + 34, + 27, + 32, + 39, + 32, + 32, + 35, + 37, + 51, + 36, + 32, + 32, + 37, + 36, + 32, + 32, + 36, + 31, + 36, + 32, + 36, + 39, + 30, + 27, + 36, + 32, + 33, + 51, + 33, + 32, + 32, + 32, + 33, + 35, + 32, + 32, + 32, + 36, + 32, + 37, + 32, + 33, + 32, + 27, + 36, + 32, + 35, + 32, + 36, + 32, + 34, + 32, + 37, + 32, + 36, + 32, + 36, + 32, + 36, + 32, + 32, + 36, + 33, + 32, + 35, + 32, + 32, + 30, + 33, + 32, + 28, + 32, + 34, + 33, + 51, + 35, + 30, + 32, + 36, + 32, + 32, + 33, + 41, + 36, + 32, + 32, + 27, + 33, + 37, + 32, + 33, + 36, + 27, + 51, + 26, + 36, + 34, + 32, + 33, + 39, + 32, + 33, + 34, + 37, + 34, + 36, + 28, + 32, + 32, + 28, + 32, + 32, + 32, + 36, + 27, + 39, + 51, + 32, + 36, + 32, + 33, + 31, + 31, + 31, + 36, + 31, + 32, + 31, + 36, + 32, + 32, + 30, + 39, + 31, + 32, + 31, + 32, + 32, + 27, + 36, + 32, + 39, + 32, + 32, + 31, + 32, + 32, + 36, + 27, + 32, + 33, + 26, + 35, + 33, + 35, + 32, + 37, + 32, + 31, + 32, + 32, + 32, + 31, + 32, + 36, + 31, + 30, + 32, + 32, + 32, + 31, + 36, + 32, + 36, + 32, + 32, + 27, + 35, + 31, + 32, + 32, + 32, + 32, + 33, + 36, + 32, + 31, + 32, + 33, + 32, + 35, + 36, + 39, + 32, + 32, + 39, + 37, + 32, + 30, + 31, + 31, + 36, + 32, + 35, + 36, + 36, + 32, + 32, + 27, + 32, + 32, + 36, + 32, + 33, + 26, + 32, + 27, + 32, + 32, + 39, + 32, + 32, + 33, + 32, + 32, + 36, + 32, + 50, + 32, + 36, + 27, + 37, + 36, + 32, + 35, + 32, + 33, + 35, + 32, + 36, + 32, + 27, + 35, + 32, + 32, + 36, + 32, + 32, + 36, + 30, + 39, + 27, + 27, + 36, + 32, + 32, + 35, + 32, + 33, + 32, + 39, + 32, + 36, + 32, + 36, + 32, + 32, + 35, + 36, + 35, + 35, + 36, + 35, + 32, + 32, + 32, + 32, + 35, + 35, + 36, + 35, + 32, + 32, + 32, + 35, + 35, + 35, + 35 + ], + "mode": "markers", + "marker": { + "color": "#19d3f3" + }, + "latsrc": "jackp:17903:e3ab9a", + "type": "scattergeo", + "lonsrc": "jackp:17903:767d41" + } + ], + "layout": { + "yaxis": { + "zeroline": false, + "gridcolor": "#dfe8f3", + "title": " " + }, + "margin": { + "l": 40, + "t": 10 + }, + "title": " ", + "barmode": "stack", + "xaxis": { + "zeroline": false, + "title": "Lat", + "showgrid": false + }, + "font": { + "color": "#506784", + "size": "12px" + }, + "geo": { + "showland": true, + "lonaxis": { + "range": [ + -180, + -50 + ] + }, + "projection": { + "type": "albers usa" + }, + "lataxis": { + "tick0": 15, + "range": [ + 15, + 80 + ] + }, + "showlakes": true, + "showsubunits": true, + "subunitwidth": 1, + "landcolor": "rgb(212,212,212)", + "countrycolor": "rgb(255,255,255)", + "subunitcolor": "rgb(255,255,255)", + "showcountries": true, + "lakecolor": "rgb(255,255,255)", + "scope": "usa", + "resolution": 50 + } + } +} diff --git a/test/image/mocks/geo_second.json b/test/image/mocks/geo_second.json index 0a9c07f5c46..6f6c0937279 100644 --- a/test/image/mocks/geo_second.json +++ b/test/image/mocks/geo_second.json @@ -83,6 +83,9 @@ "projection": { "type": "conic equal area" }, + "center": { + "lat": 35 + }, "showframe": false, "showrivers": true, "showlakes": true, diff --git a/test/image/mocks/geo_text_chart_arrays.json b/test/image/mocks/geo_text_chart_arrays.json index 690f2e95c26..deceaa3d024 100644 --- a/test/image/mocks/geo_text_chart_arrays.json +++ b/test/image/mocks/geo_text_chart_arrays.json @@ -131,6 +131,9 @@ 40, 70 ] + }, + "center": { + "lat": 57 } }, "width": 800, diff --git a/test/jasmine/tests/fx_test.js b/test/jasmine/tests/fx_test.js index 3844045d65c..bb0a392c631 100644 --- a/test/jasmine/tests/fx_test.js +++ b/test/jasmine/tests/fx_test.js @@ -76,7 +76,7 @@ describe('Fx defaults', function() { .layout; expect(layoutOut.hovermode).toBe('closest', 'hovermode to closest'); - expect(layoutOut.dragmode).toBe('zoom', 'dragmode to zoom'); + expect(layoutOut.dragmode).toBe('pan', 'dragmode to zoom'); }); it('should default (multi plot type version)', function() { diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index effb8ebc64e..ab082395d20 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -3,14 +3,14 @@ var Lib = require('@src/lib'); var Geo = require('@src/plots/geo'); var GeoAssets = require('@src/assets/geo_assets'); -var params = require('@src/plots/geo/constants'); -var supplyLayoutDefaults = require('@src/plots/geo/layout/axis_defaults'); +var constants = require('@src/plots/geo/constants'); var geoLocationUtils = require('@src/lib/geo_location_utils'); var topojsonUtils = require('@src/lib/topojson_utils'); var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var fail = require('../assets/fail_test'); var getClientPosition = require('../assets/get_client_position'); var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); @@ -29,333 +29,436 @@ function move(fromX, fromY, toX, toY, delay) { }); } +describe('Test Geo layout defaults', function() { + var layoutAttributes = Geo.layoutAttributes; + var supplyLayoutDefaults = Geo.supplyLayoutDefaults; -describe('Test geoaxes', function() { - 'use strict'; - - describe('supplyLayoutDefaults', function() { - var geoLayoutIn, - geoLayoutOut; - - beforeEach(function() { - geoLayoutOut = {}; - }); - - it('should default to lon(lat)range to params non-world scopes', function() { - var scopeDefaults = params.scopeDefaults, - scopes = Object.keys(scopeDefaults), - customLonaxisRange = [-42.21313312, 40.321321], - customLataxisRange = [-42.21313312, 40.321321]; - - var dfltLonaxisRange, dfltLataxisRange; - - scopes.forEach(function(scope) { - if(scope === 'world') return; - - dfltLonaxisRange = scopeDefaults[scope].lonaxisRange; - dfltLataxisRange = scopeDefaults[scope].lataxisRange; - - geoLayoutIn = {}; - geoLayoutOut = {scope: scope}; + var layoutIn, layoutOut, fullData; - supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); - expect(geoLayoutOut.lonaxis.range).toEqual(dfltLonaxisRange); - expect(geoLayoutOut.lataxis.range).toEqual(dfltLataxisRange); - expect(geoLayoutOut.lonaxis.tick0).toEqual(dfltLonaxisRange[0]); - expect(geoLayoutOut.lataxis.tick0).toEqual(dfltLataxisRange[0]); + beforeEach(function() { + layoutOut = {}; - geoLayoutIn = { - lonaxis: {range: customLonaxisRange}, - lataxis: {range: customLataxisRange} - }; - geoLayoutOut = {scope: scope}; + // needs a geo-ref in a trace in order to be detected + fullData = [{ type: 'scattergeo', geo: 'geo' }]; + }); - supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); - expect(geoLayoutOut.lonaxis.range).toEqual(customLonaxisRange); - expect(geoLayoutOut.lataxis.range).toEqual(customLataxisRange); - expect(geoLayoutOut.lonaxis.tick0).toEqual(customLonaxisRange[0]); - expect(geoLayoutOut.lataxis.tick0).toEqual(customLataxisRange[0]); - }); - }); + var seaFields = [ + 'showcoastlines', 'coastlinecolor', 'coastlinewidth', + 'showocean', 'oceancolor' + ]; - it('should adjust default lon(lat)range to projection.rotation in world scopes', function() { - var expectedLonaxisRange, expectedLataxisRange; + var subunitFields = [ + 'showsubunits', 'subunitcolor', 'subunitwidth' + ]; - function testOne() { - supplyLayoutDefaults(geoLayoutIn, geoLayoutOut); - expect(geoLayoutOut.lonaxis.range).toEqual(expectedLonaxisRange); - expect(geoLayoutOut.lataxis.range).toEqual(expectedLataxisRange); - } + var frameFields = [ + 'showframe', 'framecolor', 'framewidth' + ]; - geoLayoutIn = {}; - geoLayoutOut = { - scope: 'world', + it('should not coerce projection.rotation if type is albers usa', function() { + layoutIn = { + geo: { projection: { - type: 'equirectangular', + type: 'albers usa', rotation: { - lon: -75, - lat: 45 + lon: 10, + lat: 10 } } - }; - expectedLonaxisRange = [-255, 105]; // => -75 +/- 180 - expectedLataxisRange = [-45, 135]; // => 45 +/- 90 - testOne(); + } + }; - geoLayoutIn = {}; - geoLayoutOut = { - scope: 'world', + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.geo.projection.rotation).toBeUndefined(); + expect(layoutOut.geo.scope).toEqual('usa'); + }); + + it('should not coerce projection.rotation if type is albers usa (converse)', function() { + layoutIn = { + geo: { projection: { - type: 'orthographic', rotation: { - lon: -75, - lat: 45 + lon: 10, + lat: 10 } } - }; - expectedLonaxisRange = [-165, 15]; // => -75 +/- 90 - expectedLataxisRange = [-45, 135]; // => 45 +/- 90 - testOne(); + } + }; - geoLayoutIn = { - lonaxis: {range: [-42.21313312, 40.321321]}, - lataxis: {range: [-42.21313312, 40.321321]} - }; - expectedLonaxisRange = [-42.21313312, 40.321321]; - expectedLataxisRange = [-42.21313312, 40.321321]; - testOne(); - }); + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.geo.projection.rotation).toBeDefined(); + expect(layoutOut.geo.scope).toEqual('world'); }); -}); -describe('Test Geo layout defaults', function() { - 'use strict'; - - var layoutAttributes = Geo.layoutAttributes; - var supplyLayoutDefaults = Geo.supplyLayoutDefaults; - - describe('supplyLayoutDefaults', function() { - var layoutIn, layoutOut, fullData; - - beforeEach(function() { - layoutOut = {}; + it('should not coerce coastlines and ocean if type is albers usa', function() { + layoutIn = { + geo: { + projection: { + type: 'albers usa' + }, + showcoastlines: true, + showocean: true + } + }; - // needs a geo-ref in a trace in order to be detected - fullData = [{ type: 'scattergeo', geo: 'geo' }]; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + seaFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeUndefined(); }); + }); - var seaFields = [ - 'showcoastlines', 'coastlinecolor', 'coastlinewidth', - 'showocean', 'oceancolor' - ]; + it('should not coerce coastlines and ocean if type is albers usa (converse)', function() { + layoutIn = { + geo: { + showcoastlines: true, + showocean: true + } + }; - var subunitFields = [ - 'showsubunits', 'subunitcolor', 'subunitwidth' - ]; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + seaFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeDefined(); + }); + }); - var frameFields = [ - 'showframe', 'framecolor', 'framewidth' - ]; + it('should not coerce projection.parallels if type is conic', function() { + var projTypes = layoutAttributes.projection.type.values; - it('should not coerce projection.rotation if type is albers usa', function() { + function testOne(projType) { layoutIn = { geo: { projection: { - type: 'albers usa', - rotation: { - lon: 10, - lat: 10 - } + type: projType, + parallels: [10, 10] } } }; supplyLayoutDefaults(layoutIn, layoutOut, fullData); - expect(layoutOut.geo.projection.rotation).toBeUndefined(); + } + + projTypes.forEach(function(projType) { + testOne(projType); + if(projType.indexOf('conic') !== -1) { + expect(layoutOut.geo.projection.parallels).toBeDefined(); + } else { + expect(layoutOut.geo.projection.parallels).toBeUndefined(); + } }); + }); - it('should not coerce projection.rotation if type is albers usa (converse)', function() { - layoutIn = { - geo: { - projection: { - rotation: { - lon: 10, - lat: 10 - } - } - } - }; + it('should coerce subunits only when available (usa case)', function() { + layoutIn = { + geo: { scope: 'usa' } + }; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - expect(layoutOut.geo.projection.rotation).toBeDefined(); + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + subunitFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeDefined(); }); + }); - it('should not coerce coastlines and ocean if type is albers usa', function() { - layoutIn = { - geo: { - projection: { - type: 'albers usa' - }, - showcoastlines: true, - showocean: true - } - }; + it('should coerce subunits only when available (default case)', function() { + layoutIn = { geo: {} }; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - seaFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeUndefined(); - }); + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + subunitFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeUndefined(); }); + }); - it('should not coerce coastlines and ocean if type is albers usa (converse)', function() { - layoutIn = { - geo: { - showcoastlines: true, - showocean: true - } - }; + it('should coerce subunits only when available (NA case)', function() { + layoutIn = { + geo: { + scope: 'north america', + resolution: 50 + } + }; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - seaFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeDefined(); - }); + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + subunitFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeDefined(); }); + }); - it('should not coerce projection.parallels if type is conic', function() { - var projTypes = layoutAttributes.projection.type.values; + it('should coerce subunits only when available (NA case 2)', function() { + layoutIn = { + geo: { + scope: 'north america', + resolution: '50' + } + }; - function testOne(projType) { - layoutIn = { - geo: { - projection: { - type: projType, - parallels: [10, 10] - } - } - }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + subunitFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeDefined(); + }); + }); - supplyLayoutDefaults(layoutIn, layoutOut, fullData); + it('should coerce subunits only when available (NA case 2)', function() { + layoutIn = { + geo: { + scope: 'north america' } + }; - projTypes.forEach(function(projType) { - testOne(projType); - if(projType.indexOf('conic') !== -1) { - expect(layoutOut.geo.projection.parallels).toBeDefined(); - } - else { - expect(layoutOut.geo.projection.parallels).toBeUndefined(); - } - }); + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + subunitFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeUndefined(); }); + }); - it('should coerce subunits only when available (usa case)', function() { + it('should not coerce frame unless for world scope', function() { + var scopes = layoutAttributes.scope.values; + + function testOne(scope) { layoutIn = { - geo: { scope: 'usa' } + geo: { scope: scope } }; supplyLayoutDefaults(layoutIn, layoutOut, fullData); - subunitFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeDefined(); - }); + } + + scopes.forEach(function(scope) { + testOne(scope); + if(scope === 'world') { + frameFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeDefined(); + }); + } else { + frameFields.forEach(function(field) { + expect(layoutOut.geo[field]).toBeUndefined(); + }); + } }); + }); - it('should coerce subunits only when available (default case)', function() { - layoutIn = { geo: {} }; + it('should add geo data-only geos into layoutIn', function() { + layoutIn = {}; + fullData = [{ type: 'scattergeo', geo: 'geo' }]; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - subunitFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeUndefined(); + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutIn.geo).toEqual({}); + }); + + it('should add geo data-only geos into layoutIn (converse)', function() { + layoutIn = {}; + fullData = [{ type: 'scatter' }]; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutIn.geo).toBe(undefined); + }); + + describe('should default to lon(lat)range to params non-world scopes', function() { + var scopeDefaults = constants.scopeDefaults; + var scopes = Object.keys(scopeDefaults); + var customLonaxisRange = [-42.21313312, 40.321321]; + var customLataxisRange = [-42.21313312, 40.321321]; + + scopes.forEach(function(s) { + if(s === 'world') return; + + it('base case for ' + s, function() { + layoutIn = {geo: {scope: s}}; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + var dfltLonaxisRange = scopeDefaults[s].lonaxisRange; + var dfltLataxisRange = scopeDefaults[s].lataxisRange; + + expect(layoutOut.geo.lonaxis.range).toEqual(dfltLonaxisRange); + expect(layoutOut.geo.lataxis.range).toEqual(dfltLataxisRange); + expect(layoutOut.geo.lonaxis.tick0).toEqual(dfltLonaxisRange[0]); + expect(layoutOut.geo.lataxis.tick0).toEqual(dfltLataxisRange[0]); }); - }); - it('should coerce subunits only when available (NA case)', function() { - layoutIn = { - geo: { - scope: 'north america', - resolution: 50 - } - }; + it('custom case for ' + s, function() { + layoutIn = { + geo: { + scope: s, + lonaxis: {range: customLonaxisRange}, + lataxis: {range: customLataxisRange} + } + }; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - subunitFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeDefined(); + expect(layoutOut.geo.lonaxis.range).toEqual(customLonaxisRange); + expect(layoutOut.geo.lataxis.range).toEqual(customLataxisRange); + expect(layoutOut.geo.lonaxis.tick0).toEqual(customLonaxisRange[0]); + expect(layoutOut.geo.lataxis.tick0).toEqual(customLataxisRange[0]); }); }); + }); - it('should coerce subunits only when available (NA case 2)', function() { - layoutIn = { - geo: { - scope: 'north america', - resolution: '50' + describe('should adjust default lon(lat)range to projection.rotation in world scopes', function() { + var specs = [{ + geo: { + scope: 'world', + projection: { + type: 'equirectangular', + rotation: {lon: -75, lat: 45} } - }; + }, + // => -75 +/- 180 + lonRange: [-255, 105], + // => 45 +/- 90 + latRange: [-45, 135] + }, { + geo: { + scope: 'world', + projection: { + type: 'orthographic', + rotation: {lon: -75, lat: 45} + } + }, + // => -75 +/- 90 + lonRange: [-165, 15], + // => 45 +/- 90 + latRange: [-45, 135] + }, { + geo: { + lonaxis: {range: [-42.21313312, 40.321321]}, + lataxis: {range: [-42.21313312, 40.321321]} + }, + lonRange: [-42.21313312, 40.321321], + latRange: [-42.21313312, 40.321321] + }]; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - subunitFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeDefined(); + specs.forEach(function(s, i) { + it('- case ' + i, function() { + layoutIn = {geo: s.geo}; + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + expect(layoutOut.geo.lonaxis.range).toEqual(s.lonRange); + expect(layoutOut.geo.lataxis.range).toEqual(s.latRange); }); }); + }); - it('should coerce subunits only when available (NA case 2)', function() { - layoutIn = { - geo: { - scope: 'north america' - } - }; + describe('should default projection.rotation.lon to lon-center of world-scope maps', function() { + var specs = [ + { lonRange: [10, 80], projLon: 45 }, + { lonRange: [-45, -10], projLon: -27.5 }, + { lonRange: [-45, 45], projLon: 0 }, + { lonRange: [-140, 140], projLon: 0 }, + // N.B. 180 not -180 after removing ambiguity across antimeridian + { lonRange: [140, -140], projLon: 180 } + ]; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - subunitFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeUndefined(); + specs.forEach(function(s, i) { + it('- case ' + i, function() { + layoutIn = { + geo: { lonaxis: {range: s.lonRange} } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.geo.lonaxis.range) + .toEqual(s.lonRange, 'lonaxis.range'); + expect(layoutOut.geo.projection.rotation.lon) + .toEqual(s.projLon, 'computed projection rotation lon'); }); }); - it('should not coerce frame unless for world scope', function() { - var scopes = layoutAttributes.scope.values; + var scope = 'europe'; + var dflt = constants.scopeDefaults[scope].projRotate[0]; - function testOne(scope) { + specs.forEach(function(s, i) { + it('- converse ' + i, function() { layoutIn = { - geo: { scope: scope } + geo: { + scope: 'europe', + lonaxis: {range: s.lonRange} + } }; supplyLayoutDefaults(layoutIn, layoutOut, fullData); - } + expect(layoutOut.geo.lonaxis.range) + .toEqual(s.lonRange, 'lonaxis.range'); + expect(layoutOut.geo.projection.rotation.lon) + .toEqual(dflt, 'scope dflt projection rotation lon'); + }); + }); + }); - scopes.forEach(function(scope) { - testOne(scope); - if(scope === 'world') { - frameFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeDefined(); - }); - } - else { - frameFields.forEach(function(field) { - expect(layoutOut.geo[field]).toBeUndefined(); - }); - } + describe('should default center.lon', function() { + var specs = [ + { lonRange: [10, 80], projLon: 0, centerLon: 45 }, + { lonRange: [-45, -10], projLon: -20, centerLon: -27.5 }, + { lonRange: [-45, 45], projLon: 5, centerLon: 0 }, + { lonRange: [-140, 140], projLon: 0, centerLon: 0 }, + { lonRange: [140, -140], projLon: 160, centerLon: 180 } + ]; + + specs.forEach(function(s, i) { + it('to projection.rotation.lon on world maps - case ' + i, function() { + layoutIn = { + geo: { + lonaxis: {range: s.lonRange}, + projection: { + rotation: {lon: s.projLon} + } + } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.geo.lonaxis.range) + .toEqual(s.lonRange, 'lonaxis.range'); + expect(layoutOut.geo.projection.rotation.lon) + .toEqual(s.projLon, 'projection.rotation.lon'); + expect(layoutOut.geo.center.lon) + .toEqual(s.projLon, 'center lon (inherited from projection.rotation.lon'); }); }); - it('should add geo data-only geos into layoutIn', function() { - layoutIn = {}; - fullData = [{ type: 'scattergeo', geo: 'geo' }]; + var scope = 'africa'; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - expect(layoutIn.geo).toEqual({}); + specs.forEach(function(s, i) { + it('to lon-center on scoped maps - case ' + i, function() { + layoutIn = { + geo: { + scope: scope, + lonaxis: {range: s.lonRange}, + projection: { + rotation: {lon: s.projLon} + } + } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.geo.lonaxis.range) + .toEqual(s.lonRange, 'lonaxis.range'); + expect(layoutOut.geo.projection.rotation.lon) + .toEqual(s.projLon, 'projection.rotation.lon'); + expect(layoutOut.geo.center.lon) + .toEqual(s.centerLon, 'computed center lon'); + }); }); + }); - it('should add geo data-only geos into layoutIn (converse)', function() { - layoutIn = {}; - fullData = [{ type: 'scatter' }]; + describe('should default center.lat', function() { + var specs = [ + { latRange: [-90, 90], centerLat: 0 }, + { latRange: [0, 30], centerLat: 15 }, + { latRange: [-25, -5], centerLat: -15 } + ]; - supplyLayoutDefaults(layoutIn, layoutOut, fullData); - expect(layoutIn.geo).toBe(undefined); + specs.forEach(function(s, i) { + it('- case ' + i, function() { + layoutIn = { + geo: { lataxis: {range: s.latRange} } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.geo.lataxis.range) + .toEqual(s.latRange, 'lataxis.range'); + expect(layoutOut.geo.center.lat) + .toEqual(s.centerLat, 'computed center lat'); + + }); }); }); }); describe('geojson / topojson utils', function() { - 'use strict'; - function _locationToFeature(topojson, loc, locationmode) { var trace = { locationmode: locationmode }; var features = topojsonUtils.getTopojsonFeatures(trace, topojson); @@ -416,8 +519,6 @@ describe('geojson / topojson utils', function() { }); describe('Test geo interactions', function() { - 'use strict'; - afterEach(destroyGraphDiv); describe('mock geo_first.json', function() { @@ -1043,11 +1144,204 @@ describe('Test geo interactions', function() { done(); }); }); - }); }); -}); + it('should not throw during hover when out-of-range pts are present in *albers usa* map', function(done) { + var gd = createGraphDiv(); + var fig = Lib.extendDeep({}, require('@mocks/geo_scattergeo-out-of-usa.json')); + fig.layout.width = 700; + fig.layout.height = 500; + + Plotly.plot(gd, fig).then(function() { + mouseEvent('mousemove', 350, 250); + expect(d3.selectAll('g.hovertext').size()).toEqual(1); + }) + .catch(fail) + .then(done); + }); + + it('@noCI should clear hover label when cursor slips off subplot', function(done) { + var gd = createGraphDiv(); + var fig = Lib.extendDeep({}, require('@mocks/geo_orthographic.json')); + + function _assert(msg, hoverLabelCnt) { + expect(d3.selectAll('g.hovertext').size()) + .toBe(hoverLabelCnt, msg); + } + + var px = 390; + var py = 290; + var cnt = 0; + + Plotly.plot(gd, fig).then(function() { + gd.on('plotly_unhover', function() { cnt++; }); + + mouseEvent('mousemove', px, py); + _assert('base state', 1); + + return new Promise(function(resolve) { + var interval = setInterval(function() { + px += 2; + mouseEvent('mousemove', px, py); + + if(px < 402) { + _assert('- px ' + px, 1); + expect(cnt).toBe(0, 'no plotly_unhover event so far'); + } else { + _assert('- px ' + px, 0); + expect(cnt).toBe(1, 'plotly_unhover event count'); + + clearInterval(interval); + resolve(); + } + }, 100); + }); + }) + .catch(fail) + .then(done); + }); + + it('should not confuse positions on either side of the globe', function(done) { + var gd = createGraphDiv(); + var fig = Lib.extendDeep({}, require('@mocks/geo_orthographic.json')); + + fig.data[0].visible = false; + fig.layout.geo.projection.rotation = {lon: -75, lat: 90}; + + function check(p, hoverLabelCnt) { + mouseEvent('mousemove', p[0], p[1]); + + var invert = gd._fullLayout.geo._subplot.projection.invert; + var lonlat = invert(p); + + expect(d3.selectAll('g.hovertext').size()) + .toBe(hoverLabelCnt, 'for ' + lonlat); + + delete gd._lastHoverTime; + } + + Plotly.plot(gd, fig).then(function() { + var px = 255; + + check([px, 163], 0); + check([px, 360], 1); + }) + .catch(fail) + .then(done); + }); + + it('should plot to scope defaults when user setting lead to NaN map bounds', function(done) { + var gd = createGraphDiv(); + + spyOn(Lib, 'warn'); + + Plotly.plot(gd, [{ + type: 'scattergeo', + lon: [0], + lat: [0] + }], { + geo: { + projection: { + type: 'kavrayskiy7', + rotation: { + lat: 38.794799, + lon: -81.622334, + } + }, + center: { + lat: -81 + }, + lataxis: { + range: [38.794799, 45.122292] + }, + lonaxis: { + range: [-82.904731, -81.622334] + } + }, + width: 700, + heigth: 500 + }) + .then(function() { + var geoLayout = gd._fullLayout.geo; + var geo = geoLayout._subplot; + + expect(geoLayout.projection.rotation).toEqual({ + lon: 0, lat: 0, roll: 0, + }); + expect(geoLayout.center).toEqual({ + lon: 0, lat: 0 + }); + expect(geoLayout.lonaxis.range).toEqual([-180, 180]); + expect(geoLayout.lataxis.range).toEqual([-90, 90]); + + expect(geo.viewInitial).toEqual({ + 'projection.rotation.lon': 0, + 'center.lon': 0, + 'center.lat': 0, + 'projection.scale': 1 + }); + + expect(Lib.warn).toHaveBeenCalledTimes(1); + expect(Lib.warn).toHaveBeenCalledWith( + 'Invalid geo settings, relayout\'ing to default view.' + ); + }) + .catch(fail) + .then(done); + }); + + it('should get hover right for choropleths involving landmasses that cross antimeridian', function(done) { + var gd = createGraphDiv(); + + function check(lonlat, hoverLabelCnt, msg) { + var projection = gd._fullLayout.geo._subplot.projection; + var px = projection(lonlat); + + mouseEvent('mousemove', px[0], px[1]); + expect(d3.selectAll('g.hovertext').size()).toBe(hoverLabelCnt, msg); + + delete gd._lastHoverTime; + } + + Plotly.newPlot(gd, [{ + type: 'choropleth', + locations: ['RUS', 'FJI', 'ATA'], + z: [0, 1, 2] + }]) + .then(function() { + check([81, 66], 1, 'spot in north-central Russia that polygon.contains gets wrong before +360 shift'); + check([-80, 66], 0, 'spot north of Hudson bay that polygon.contains believe is in Russia before before +360 shift'); + + return Plotly.relayout(gd, 'geo.projection.rotation.lon', 180); + }) + .then(function() { + check([-174, 65], 1, 'spot in Russia mainland beyond antimeridian'); + + return Plotly.relayout(gd, { + 'geo.center.lat': -16, + 'geo.projection.scale': 17 + }); + }) + .then(function() { + check([179, -16.6], 1, 'spot on Fiji island that cross antimeridian west of antimeridian'); + check([-179.9, -16.2], 1, 'spot on Fiji island that cross antimeridian east of antimeridian'); + + return Plotly.relayout(gd, { + 'geo.center.lat': null, + 'geo.projection': { + type: 'orthographic', + rotation: {lat: -90} + } + }); + }) + .then(function() { + check([-150, -89], 1, 'spot in Antarctica that requires *stitching*'); + }) + .catch(fail) + .then(done); + }); +}); describe('Test event property of interactions on a geo plot:', function() { var mock = require('@mocks/geo_scattergeo-locations.json'); @@ -1244,3 +1538,485 @@ describe('Test event property of interactions on a geo plot:', function() { }); }); }); + +describe('Test geo base layers', function() { + afterEach(destroyGraphDiv); + + it('should clear obsolete features and layers on *geo.scope* relayout calls', function(done) { + var gd = createGraphDiv(); + + function _assert(geojson, layers) { + var cd0 = gd.calcdata[0]; + var subplot = gd._fullLayout.geo._subplot; + + expect(cd0[0].geojson).negateIf(geojson[0]).toBe(null); + expect(cd0[1].geojson).negateIf(geojson[1]).toBe(null); + + expect(Object.keys(subplot.layers).length).toEqual(layers.length, '# of layers'); + + d3.select(gd).selectAll('.geo > .layer').each(function(d, i) { + expect(d).toBe(layers[i], 'layer ' + d + ' at position ' + i); + }); + } + + Plotly.plot(gd, [{ + type: 'choropleth', + locations: ['CAN', 'FRA'], + z: [10, 20] + }], { + geo: {showframe: true} + }) + .then(function() { + _assert( + [true, true], + ['bg', 'coastlines', 'frame', 'backplot', 'frontplot'] + ); + return Plotly.relayout(gd, 'geo.scope', 'europe'); + }) + .then(function() { + _assert( + // 'CAN' is not drawn on 'europe' scope + [false, true], + // 'frame' is not drawn on scoped maps + // 'countries' are there by default on scoped maps + ['bg', 'countries', 'backplot', 'frontplot'] + ); + return Plotly.relayout(gd, 'geo.scope', 'africa'); + }) + .then(function() { + _assert( + [false, false], + ['bg', 'countries', 'backplot', 'frontplot'] + ); + return Plotly.relayout(gd, 'geo.scope', 'world'); + }) + .then(function() { + _assert( + [true, true], + ['bg', 'coastlines', 'frame', 'backplot', 'frontplot'] + ); + }) + .catch(fail) + .then(done); + }); +}); + +describe('Test geo zoom/pan/drag interactions:', function() { + var gd; + var eventData; + var dblClickCnt = 0; + + afterEach(destroyGraphDiv); + + function plot(fig) { + gd = createGraphDiv(); + + return Plotly.plot(gd, fig).then(function() { + gd.on('plotly_relayout', function(d) { eventData = d; }); + gd.on('plotly_doubleclick', function() { dblClickCnt++; }); + }); + } + + function assertEventData(msg, eventKeys) { + if(eventKeys === 'dblclick') { + expect(dblClickCnt).toBe(1, msg + 'double click got fired'); + expect(eventData).toBeDefined(msg + 'relayout is fired on double clicks'); + } else { + expect(dblClickCnt).toBe(0, 'double click not fired'); + + if(Array.isArray(eventKeys)) { + expect(Object.keys(eventData || {}).length) + .toBe(Object.keys(eventKeys).length, msg + '# of event data keys'); + eventKeys.forEach(function(k) { + expect((eventData || {})[k]).toBeDefined(msg + 'event data key ' + k); + }); + } else { + expect(eventData).toBeUndefined(msg + 'relayout not fired'); + } + } + + eventData = undefined; + dblClickCnt = 0; + } + + + function drag(path) { + var len = path.length; + + mouseEvent('mousemove', path[0][0], path[0][1]); + mouseEvent('mousedown', path[0][0], path[0][1]); + + path.slice(1, len).forEach(function(pt) { + mouseEvent('mousemove', pt[0], pt[1]); + }); + + mouseEvent('mouseup', path[len - 1][0], path[len - 1][1]); + } + + function scroll(pos, delta) { + return new Promise(function(resolve) { + mouseEvent('mousemove', pos[0], pos[1]); + mouseEvent('scroll', pos[0], pos[1], {deltaX: delta[0], deltaY: delta[1]}); + setTimeout(resolve, 100); + }); + } + + function dblClick(pos) { + return new Promise(function(resolve) { + mouseEvent('dblclick', pos[0], pos[1]); + setTimeout(resolve, 100); + }); + } + + it('should work for non-clipped projections', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_winkel-tripel')); + fig.layout.width = 700; + fig.layout.height = 500; + fig.layout.dragmode = 'pan'; + + function _assert(step, attr, proj, eventKeys) { + var msg = '[' + step + '] '; + + var geoLayout = gd._fullLayout.geo; + var rotation = geoLayout.projection.rotation; + var center = geoLayout.center; + var scale = geoLayout.projection.scale; + + expect(rotation.lon).toBeCloseTo(attr[0][0], 1, msg + 'rotation.lon'); + expect(rotation.lat).toBeCloseTo(attr[0][1], 1, msg + 'rotation.lat'); + expect(center.lon).toBeCloseTo(attr[1][0], 1, msg + 'center.lon'); + expect(center.lat).toBeCloseTo(attr[1][1], 1, msg + 'center.lat'); + expect(scale).toBeCloseTo(attr[2], 1, msg + 'zoom'); + + var geo = geoLayout._subplot; + var rotate = geo.projection.rotate(); + var translate = geo.projection.translate(); + var _center = geo.projection.center(); + var _scale = geo.projection.scale(); + + expect(rotate[0]).toBeCloseTo(proj[0][0], 0, msg + 'rotate[0]'); + expect(rotate[1]).toBeCloseTo(proj[0][1], 0, msg + 'rotate[1]'); + expect(translate[0]).toBeCloseTo(proj[1][0], 0, msg + 'translate[0]'); + expect(translate[1]).toBeCloseTo(proj[1][1], 0, msg + 'translate[1]'); + expect(_center[0]).toBeCloseTo(proj[2][0], 0, msg + 'center[0]'); + expect(_center[1]).toBeCloseTo(proj[2][1], 0, msg + 'center[1]'); + expect(_scale).toBeCloseTo(proj[3], 0, msg + 'scale'); + + assertEventData(msg, eventKeys); + } + + plot(fig).then(function() { + _assert('base', [ + [-90, 0], [-90, 0], 1 + ], [ + [90, 0], [350, 260], [0, 0], 101.9 + ], undefined); + return drag([[350, 250], [400, 250]]); + }) + .then(function() { + _assert('after east-west drag', [ + [-124.4, 0], [-124.4, 0], 1 + ], [ + [124.4, 0], [350, 260], [0, 0], 101.9 + ], [ + 'geo.projection.rotation.lon', 'geo.center.lon' + ]); + return drag([[400, 250], [400, 300]]); + }) + .then(function() { + _assert('after north-south drag', [ + [-124.4, 0], [-124.4, 28.1], 1 + ], [ + [124.4, 0], [350, 310], [0, 0], 101.9 + ], [ + 'geo.center.lat' + ]); + return scroll([200, 250], [-200, -200]); + }) + .then(function() { + _assert('after off-center scroll', [ + [-151.2, 0], [-151.2, 29.5], 1.3 + ], [ + [151.2, 0], [350, 329.2], [0, 0], 134.4 + ], [ + 'geo.projection.rotation.lon', + 'geo.center.lon', 'geo.center.lat', + 'geo.projection.scale' + ]); + return Plotly.relayout(gd, 'geo.showocean', false); + }) + .then(function() { + _assert('after some relayout call that causes a replot', [ + [-151.2, 0], [-151.2, 29.5], 1.3 + ], [ + // converts translate (px) to center (lonlat) + [151.2, 0], [350, 260], [0, 29.5], 134.4 + ], [ + 'geo.showocean' + ]); + return dblClick([350, 250]); + }) + .then(function() { + // resets to initial view + _assert('after double click', [ + [-90, 0], [-90, 0], 1 + ], [ + [90, 0], [350, 260], [0, 0], 101.9 + ], 'dblclick'); + }) + .catch(fail) + .then(done); + }); + + it('should work for clipped projections', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_orthographic')); + fig.layout.dragmode = 'pan'; + + // of layout width = height = 500 + + function _assert(step, attr, proj, eventKeys) { + var msg = '[' + step + '] '; + + var geoLayout = gd._fullLayout.geo; + var rotation = geoLayout.projection.rotation; + var scale = geoLayout.projection.scale; + + expect(rotation.lon).toBeCloseTo(attr[0][0], 0, msg + 'rotation.lon'); + expect(rotation.lat).toBeCloseTo(attr[0][1], 0, msg + 'rotation.lat'); + expect(scale).toBeCloseTo(attr[1], 1, msg + 'zoom'); + + var geo = geoLayout._subplot; + var rotate = geo.projection.rotate(); + var _scale = geo.projection.scale(); + + expect(rotate[0]).toBeCloseTo(proj[0][0], 0, msg + 'rotate[0]'); + expect(rotate[1]).toBeCloseTo(proj[0][1], 0, msg + 'rotate[1]'); + expect(_scale).toBeCloseTo(proj[1], 0, msg + 'scale'); + + assertEventData(msg, eventKeys); + } + + plot(fig).then(function() { + _assert('base', [ + [-75, 45], 1 + ], [ + [75, -45], 160 + ], undefined); + return drag([[250, 250], [300, 250]]); + }) + .then(function() { + _assert('after east-west drag', [ + [-103.7, 49.3], 1 + ], [ + [103.7, -49.3], 160 + ], [ + 'geo.projection.rotation.lon', 'geo.projection.rotation.lat' + ]); + return drag([[250, 250], [300, 300]]); + }) + .then(function() { + _assert('after NW-SE drag', [ + [-135.5, 73.8], 1 + ], [ + [135.5, -73.8], 160 + ], [ + 'geo.projection.rotation.lon', 'geo.projection.rotation.lat' + ]); + return scroll([300, 300], [-200, -200]); + }) + .then(function() { + _assert('after scroll', [ + [-126.2, 67.1], 1.3 + ], [ + [126.2, -67.1], 211.1 + ], [ + 'geo.projection.rotation.lon', 'geo.projection.rotation.lat', + 'geo.projection.scale' + ]); + return Plotly.relayout(gd, 'geo.showocean', false); + }) + .then(function() { + _assert('after some relayout call that causes a replot', [ + [-126.2, 67.1], 1.3 + ], [ + [126.2, -67.1], 211.1 + ], [ + 'geo.showocean' + ]); + return dblClick([350, 250]); + }) + .then(function() { + // resets to initial view + _assert('after double click', [ + [-75, 45], 1 + ], [ + [75, -45], 160 + ], 'dblclick'); + }) + .catch(fail) + .then(done); + }); + + it('should work for scoped projections', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_europe-bubbles')); + fig.layout.geo.resolution = 110; + fig.layout.dragmode = 'pan'; + + // of layout width = height = 500 + + function _assert(step, attr, proj, eventKeys) { + var msg = '[' + step + '] '; + + var geoLayout = gd._fullLayout.geo; + var center = geoLayout.center; + var scale = geoLayout.projection.scale; + + expect(center.lon).toBeCloseTo(attr[0][0], -0.5, msg + 'center.lon'); + expect(center.lat).toBeCloseTo(attr[0][1], -0.5, msg + 'center.lat'); + expect(scale).toBeCloseTo(attr[1], 1, msg + 'zoom'); + + var geo = geoLayout._subplot; + var translate = geo.projection.translate(); + var _center = geo.projection.center(); + var _scale = geo.projection.scale(); + + expect(translate[0]).toBeCloseTo(proj[0][0], -0.75, msg + 'translate[0]'); + expect(translate[1]).toBeCloseTo(proj[0][1], -0.75, msg + 'translate[1]'); + expect(_center[0]).toBeCloseTo(proj[1][0], -0.5, msg + 'center[0]'); + expect(_center[1]).toBeCloseTo(proj[1][1], -0.5, msg + 'center[1]'); + expect(_scale).toBeCloseTo(proj[2], -1, msg + 'scale'); + + assertEventData(msg, eventKeys); + } + + plot(fig).then(function() { + _assert('base', [ + [15, 57.5], 1, + ], [ + [247, 260], [0, 57.5], 292.2 + ], undefined); + return drag([[250, 250], [200, 200]]); + }) + .then(function() { + _assert('after SW-NE drag', [ + [30.9, 46.2], 1 + ], [ + // changes translate(), but not center() + [197, 210], [0, 57.5], 292.2 + ], [ + 'geo.center.lon', 'geo.center.lon' + ]); + return scroll([300, 300], [-200, -200]); + }) + .then(function() { + _assert('after scroll', [ + [34.3, 43.6], 1.3 + ], [ + [164.1, 181.2], [0, 57.5], 385.5 + ], [ + 'geo.center.lon', 'geo.center.lon', 'geo.projection.scale' + ]); + return Plotly.relayout(gd, 'geo.showlakes', true); + }) + .then(function() { + _assert('after some relayout call that causes a replot', [ + [34.3, 43.6], 1.3 + ], [ + // changes are now reflected in 'center' + [247, 260], [19.3, 43.6], 385.5 + ], [ + 'geo.showlakes' + ]); + return dblClick([250, 250]); + }) + .then(function() { + _assert('after double click', [ + [15, 57.5], 1, + ], [ + [247, 260], [0, 57.5], 292.2 + ], 'dblclick'); + }) + .catch(fail) + .then(done); + }); + + it('should work for *albers usa* projections', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-usa')); + fig.layout.dragmode = 'pan'; + + // layout width = 870 + // layout height = 598 + + function _assert(step, attr, proj, eventKeys) { + var msg = '[' + step + '] '; + + var geoLayout = gd._fullLayout.geo; + var center = geoLayout.center; + var scale = geoLayout.projection.scale; + + expect(center.lon).toBeCloseTo(attr[0][0], 1, msg + 'center.lon'); + expect(center.lat).toBeCloseTo(attr[0][1], 1, msg + 'center.lat'); + expect(scale).toBeCloseTo(attr[1], 1, msg + 'zoom'); + + // albersUsa projection does not have a center() method + var geo = geoLayout._subplot; + var translate = geo.projection.translate(); + var _scale = geo.projection.scale(); + + expect(translate[0]).toBeCloseTo(proj[0][0], -1, msg + 'translate[0]'); + expect(translate[1]).toBeCloseTo(proj[0][1], -1, msg + 'translate[1]'); + expect(_scale).toBeCloseTo(proj[1], -1.5, msg + 'scale'); + + assertEventData(msg, eventKeys); + } + + plot(fig).then(function() { + _assert('base', [ + [-96.6, 38.7], 1, + ], [ + [416, 309], 738.5 + ], undefined); + return drag([[250, 250], [200, 200]]); + }) + .then(function() { + _assert('after NW-SE drag', [ + [-91.8, 34.8], 1, + ], [ + [366, 259], 738.5 + ], [ + 'geo.center.lon', 'geo.center.lon' + ]); + return scroll([300, 300], [-200, -200]); + }) + .then(function() { + _assert('after scroll', [ + [-94.5, 35.0], 1.3 + ], [ + [387.1, 245.9], 974.4 + ], [ + 'geo.center.lon', 'geo.center.lon', 'geo.projection.scale' + ]); + return Plotly.relayout(gd, 'geo.showlakes', true); + }) + .then(function() { + _assert('after some relayout call that causes a replot', [ + [-94.5, 35.0], 1.3 + ], [ + // new center values are reflected in translate() + [387.1, 245.9], 974.4 + ], [ + 'geo.showlakes' + ]); + return dblClick([250, 250]); + }) + .then(function() { + _assert('after double click', [ + [-96.6, 38.7], 1, + ], [ + [416, 309], 738.5 + ], 'dblclick'); + }) + .catch(fail) + .then(done); + }); +}); diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index c6c08ec98d9..3246769b229 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -173,10 +173,10 @@ describe('ModeBar', function() { expectedButtonCount += group.length; }); - expect(modeBar.hasButtons(buttons)).toBe(true); - expect(countGroups(modeBar)).toEqual(expectedGroupCount); - expect(countButtons(modeBar)).toEqual(expectedButtonCount); - expect(countLogo(modeBar)).toEqual(1); + expect(modeBar.hasButtons(buttons)).toBe(true, 'modeBar.hasButtons'); + expect(countGroups(modeBar)).toEqual(expectedGroupCount, 'correct group count'); + expect(countButtons(modeBar)).toEqual(expectedButtonCount, 'correct button count'); + expect(countLogo(modeBar)).toEqual(1, 'correct logo count'); } it('creates mode bar (unselectable cartesian version)', function() { @@ -256,6 +256,7 @@ describe('ModeBar', function() { it('creates mode bar (geo version)', function() { var buttons = getButtons([ ['toImage', 'sendDataToCloud'], + ['pan2d'], ['zoomInGeo', 'zoomOutGeo', 'resetGeo'], ['hoverClosestGeo'] ]); @@ -269,6 +270,29 @@ describe('ModeBar', function() { checkButtons(modeBar, buttons, 1); }); + it('creates mode bar (geo + selected version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['pan2d', 'select2d', 'lasso2d'], + ['zoomInGeo', 'zoomOutGeo', 'resetGeo'], + ['hoverClosestGeo'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._basePlotModules = [{ name: 'geo' }]; + gd._fullData = [{ + type: 'scattergeo', + visible: true, + mode: 'markers', + _module: {selectPoints: true} + }]; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + it('creates mode bar (mapbox version)', function() { var buttons = getButtons([ ['toImage', 'sendDataToCloud'], diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index b2be82a162c..d6c2dd4c0c5 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -12,41 +12,46 @@ var touchEvent = require('../assets/touch_event'); var LONG_TIMEOUT_INTERVAL = 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL; +function drag(path, options) { + var len = path.length; -describe('select box and lasso', function() { - var mock = require('@mocks/14.json'); - - var selectPath = [[93, 193], [143, 193]]; - var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]]; + if(!options) options = {type: 'mouse'}; - afterEach(destroyGraphDiv); + if(options.type === 'touch') { + touchEvent('touchstart', path[0][0], path[0][1]); - function drag(path, options) { - var len = path.length; + path.slice(1, len).forEach(function(pt) { + touchEvent('touchmove', pt[0], pt[1]); + }); - if(!options) options = {type: 'mouse'}; + touchEvent('touchend', path[len - 1][0], path[len - 1][1]); + return; + } - if(options.type === 'touch') { - touchEvent('touchstart', path[0][0], path[0][1]); + mouseEvent('mousemove', path[0][0], path[0][1]); + mouseEvent('mousedown', path[0][0], path[0][1]); - path.slice(1, len).forEach(function(pt) { - touchEvent('touchmove', pt[0], pt[1]); - }); + path.slice(1, len).forEach(function(pt) { + mouseEvent('mousemove', pt[0], pt[1]); + }); - touchEvent('touchend', path[len - 1][0], path[len - 1][1]); + mouseEvent('mouseup', path[len - 1][0], path[len - 1][1]); +} - return; - } +function assertSelectionNodes(cornerCnt, outlineCnt) { + expect(d3.selectAll('.zoomlayer > .zoombox-corners').size()) + .toBe(cornerCnt, 'selection corner count'); + expect(d3.selectAll('.zoomlayer > .select-outline').size()) + .toBe(outlineCnt, 'selection outline count'); +} - mouseEvent('mousemove', path[0][0], path[0][1]); - mouseEvent('mousedown', path[0][0], path[0][1]); +describe('Test select box and lasso in general:', function() { + var mock = require('@mocks/14.json'); - path.slice(1, len).forEach(function(pt) { - mouseEvent('mousemove', pt[0], pt[1]); - }); + var selectPath = [[93, 193], [143, 193]]; + var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]]; - mouseEvent('mouseup', path[len - 1][0], path[len - 1][1]); - } + afterEach(destroyGraphDiv); function assertRange(actual, expected) { var PRECISION = 4; @@ -72,13 +77,6 @@ describe('select box and lasso', function() { }); } - function assertSelectionNodes(cornerCnt, outlineCnt) { - expect(d3.selectAll('.zoomlayer > .zoombox-corners').size()) - .toBe(cornerCnt, 'selection corner count'); - expect(d3.selectAll('.zoomlayer > .select-outline').size()) - .toBe(outlineCnt, 'selection outline count'); - } - describe('select elements', function() { var mockCopy = Lib.extendDeep({}, mock); mockCopy.layout.dragmode = 'select'; @@ -430,195 +428,264 @@ describe('select box and lasso', function() { }) .then(done); }); +}); - it('should work on scatterternary traces', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/ternary_simple')); - var gd = createGraphDiv(); - var pts = []; +describe('Test select box and lasso per trace:', function() { + var gd; + var eventData; - fig.layout.width = 800; - fig.layout.dragmode = 'select'; + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + function makeAssertPoints(keys) { + var callNumber = 0; + + return function(expected) { + var msg = '(call #' + callNumber + ') '; + var pts = (eventData || {}).points || []; - function assertPoints(expected) { - expect(pts.length).toBe(expected.length, 'selected points length'); + expect(pts.length).toBe(expected.length, msg + 'selected points length'); pts.forEach(function(p, i) { - var e = expected[i]; - expect(p.a).toBe(e.a, 'selected pt a val'); - expect(p.b).toBe(e.b, 'selected pt b val'); - expect(p.c).toBe(e.c, 'selected pt c val'); + var e = expected[i] || []; + keys.forEach(function(k, j) { + expect(p[k]) + .toBe(e[j], msg + 'selected pt ' + i + ' - ' + k + ' val'); + }); }); - pts = []; - } - Plotly.plot(gd, fig).then(function() { - gd.on('plotly_selected', function(data) { - pts = data.points; - }); + callNumber++; + }; + } - assertSelectionNodes(0, 0); - drag([[400, 200], [445, 235]]); + function makeAssertRanges(subplot, tol) { + tol = tol || 1; + var callNumber = 0; + + return function(expected) { + var msg = '(call #' + callNumber + ') '; + var ranges = (eventData.range || {})[subplot] || []; + + expect(ranges) + .toBeCloseTo2DArray(expected, tol, msg + 'select box range for ' + subplot); + + callNumber++; + }; + } + + function makeAssertLassoPoints(subplot, tol) { + tol = tol || 1; + var callNumber = 0; + + return function(expected) { + var msg = '(call #' + callNumber + ') '; + var lassoPoints = (eventData.lassoPoints || {})[subplot] || []; + + expect(lassoPoints) + .toBeCloseTo2DArray(expected, tol, msg + 'lasso points for ' + subplot); + + callNumber++; + }; + } + + function _run(dragPath, afterDragFn, dblClickPos) { + afterDragFn = afterDragFn || function() {}; + dblClickPos = dblClickPos || [250, 200]; + + var selectingCnt = 0; + var selectedCnt = 0; + var deselectCnt = 0; + + gd.once('plotly_selecting', function() { + assertSelectionNodes(1, 2); + selectingCnt++; + }); + + gd.once('plotly_selected', function(d) { assertSelectionNodes(0, 2); - assertPoints([{ a: 0.5, b: 0.25, c: 0.25 }]); + selectedCnt++; + eventData = d; + }); + + gd.once('plotly_deselect', function() { + deselectCnt++; + assertSelectionNodes(0, 0); + }); + + assertSelectionNodes(0, 0); + drag(dragPath); + afterDragFn(); + + return doubleClick(dblClickPos[0], dblClickPos[1]).then(function() { + expect(selectingCnt).toBe(1, 'plotly_selecting call count'); + expect(selectedCnt).toBe(1, 'plotly_selected call count'); + expect(deselectCnt).toBe(1, 'plotly_deselect call count'); + }); + } + + function _runNoSelect(dragPath, afterDragFn, dblClickPos) { + afterDragFn = afterDragFn || function() {}; + dblClickPos = dblClickPos || [250, 200]; + + var selectingCnt = 0; + var selectedCnt = 0; + var deselectCnt = 0; + + gd.once('plotly_selecting', function() { + selectingCnt++; + }); + + gd.once('plotly_selected', function() { + selectedCnt++; + }); + + gd.once('plotly_deselect', function() { + deselectCnt++; + }); + assertSelectionNodes(0, 0); + drag(dragPath); + afterDragFn(); + + return doubleClick(dblClickPos[0], dblClickPos[1]).then(function() { + expect(selectingCnt).toBe(0, 'plotly_selecting call count'); + expect(selectedCnt).toBe(0, 'plotly_selected call count'); + expect(deselectCnt).toBe(0, 'plotly_deselect call count'); + }); + } + + it('should work on scatterternary traces', function(done) { + var assertPoints = makeAssertPoints(['a', 'b', 'c']); + + var fig = Lib.extendDeep({}, require('@mocks/ternary_simple')); + fig.layout.width = 800; + fig.layout.dragmode = 'select'; + + Plotly.plot(gd, fig).then(function() { + return _run([[400, 200], [445, 235]], function() { + assertPoints([[0.5, 0.25, 0.25]]); + }, [380, 180]); + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'lasso'); }) .then(function() { - assertSelectionNodes(0, 0); - drag([[400, 200], [445, 200], [445, 235], [400, 235], [400, 200]]); - assertSelectionNodes(0, 2); - assertPoints([{ a: 0.5, b: 0.25, c: 0.25 }]); - + return _run([[400, 200], [445, 200], [445, 235], [400, 235], [400, 200]], function() { + assertPoints([[0.5, 0.25, 0.25]]); + }, [380, 180]); + }) + .then(function() { // should work after a relayout too return Plotly.relayout(gd, 'width', 400); }) .then(function() { - assertSelectionNodes(0, 0); - drag([[200, 200], [230, 200], [230, 230], [200, 230], [200, 200]]); - assertSelectionNodes(0, 2); - assertPoints([{ a: 0.5, b: 0.25, c: 0.25 }]); + return _run([[200, 200], [230, 200], [230, 230], [200, 230], [200, 200]], function() { + assertPoints([[0.5, 0.25, 0.25]]); + }, [180, 180]); }) .catch(fail) .then(done); }); it('should work on scattercarpet traces', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/scattercarpet')); - var gd = createGraphDiv(); - var pts = []; + var assertPoints = makeAssertPoints(['a', 'b']); + var fig = Lib.extendDeep({}, require('@mocks/scattercarpet')); fig.layout.dragmode = 'select'; - function assertPoints(expected) { - expect(pts.length).toBe(expected.length, 'selected points length'); - - pts.forEach(function(p, i) { - var e = expected[i]; - expect(p.a).toBe(e.a, 'selected pt a val'); - expect(p.b).toBe(e.b, 'selected pt b val'); - }); - pts = []; - } - Plotly.plot(gd, fig).then(function() { - gd.on('plotly_selected', function(data) { - pts = data.points; + return _run([[300, 200], [400, 250]], function() { + assertPoints([[0.2, 1.5]]); }); - - assertSelectionNodes(0, 0); - drag([[300, 200], [400, 250]]); - assertSelectionNodes(0, 2); - assertPoints([{ a: 0.2, b: 1.5 }]); - + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'lasso'); }) .then(function() { - assertSelectionNodes(0, 0); - drag([[300, 200], [400, 200], [400, 250], [300, 250], [300, 200]]); - assertSelectionNodes(0, 2); - assertPoints([{ a: 0.2, b: 1.5 }]); + return _run([[300, 200], [400, 200], [400, 250], [300, 250], [300, 200]], function() { + assertPoints([[0.2, 1.5]]); + }); }) .catch(fail) .then(done); }); it('@noCI should work on scattermapbox traces', function(done) { - var fig = Lib.extendDeep({}, require('@mocks/mapbox_bubbles-text')); - var gd = createGraphDiv(); - var eventData; + var assertPoints = makeAssertPoints(['lon', 'lat']); + var assertRanges = makeAssertRanges('mapbox'); + var assertLassoPoints = makeAssertLassoPoints('mapbox'); + var fig = Lib.extendDeep({}, require('@mocks/mapbox_bubbles-text')); fig.layout.dragmode = 'select'; fig.config = { mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN }; - function assertPoints(expected) { - var pts = eventData.points || []; - - expect(pts.length).toBe(expected.length, 'selected points length'); - - pts.forEach(function(p, i) { - var e = expected[i]; - expect(p.lon).toBe(e.lon, 'selected pt lon val'); - expect(p.lat).toBe(e.lat, 'selected pt lat val'); - }); - } - - function assertRanges(expected) { - var ranges = (eventData.range || {}).mapbox || []; - expect(ranges).toBeCloseTo2DArray(expected, 1, 'select box range (in lon/lat)'); - } - - function assertLassoPoints(expected) { - var lassoPoints = (eventData.lassoPoints || {}).mapbox || []; - expect(lassoPoints).toBeCloseTo2DArray(expected, 1, 'lasso points (in lon/lat)'); - } - Plotly.plot(gd, fig).then(function() { - var selectingCnt = 0; - var selectedCnt = 0; - var deselectCnt = 0; - - gd.once('plotly_selecting', function() { - assertSelectionNodes(1, 2); - selectingCnt++; - }); - - gd.once('plotly_selected', function(d) { - assertSelectionNodes(0, 2); - selectedCnt++; - eventData = d; - }); - - gd.once('plotly_deselect', function() { - deselectCnt++; - assertSelectionNodes(0, 0); - }); - - drag([[370, 120], [500, 200]]); - assertPoints([{lon: 30, lat: 30}]); - assertRanges([[21.99, 34.55], [38.14, 25.98]]); - - return doubleClick(250, 200).then(function() { - expect(selectingCnt).toBe(1, 'plotly_selecting call count'); - expect(selectedCnt).toBe(1, 'plotly_selected call count'); - expect(deselectCnt).toBe(1, 'plotly_deselect call count'); + return _run([[370, 120], [500, 200]], function() { + assertPoints([[30, 30]]); + assertRanges([[21.99, 34.55], [38.14, 25.98]]); }); }) .then(function() { return Plotly.relayout(gd, 'dragmode', 'lasso'); }) .then(function() { - var selectingCnt = 0; - var selectedCnt = 0; - var deselectCnt = 0; - - gd.once('plotly_selecting', function() { - assertSelectionNodes(1, 2); - selectingCnt++; - }); - - gd.once('plotly_selected', function(d) { - assertSelectionNodes(0, 2); - selectedCnt++; - eventData = d; + return _run([[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], function() { + assertPoints([[20, 20]]); + assertLassoPoints([ + [13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97] + ]); }); + }) + .then(function() { + // make selection handlers don't get called in 'pan' dragmode + return Plotly.relayout(gd, 'dragmode', 'pan'); + }) + .then(function() { + return _runNoSelect([[370, 120], [500, 200]]); + }) + .catch(fail) + .then(done); + }, LONG_TIMEOUT_INTERVAL); - gd.once('plotly_deselect', function() { - deselectCnt++; - assertSelectionNodes(0, 0); + it('should work on scattergeo traces', function(done) { + var assertPoints = makeAssertPoints(['lon', 'lat']); + var assertRanges = makeAssertRanges('geo'); + var assertLassoPoints = makeAssertLassoPoints('geo'); + + Plotly.plot(gd, [{ + type: 'scattergeo', + lon: [10, 20, 30], + lat: [10, 20, 30] + }, { + type: 'scattergeo', + lon: [-10, -20, -30], + lat: [10, 20, 30] + }], { + showlegend: false, + dragmode: 'select', + width: 800, + height: 600 + }) + .then(function() { + return _run([[350, 200], [450, 400]], function() { + assertPoints([[10, 10], [20, 20], [-10, 10], [-20, 20]]); + assertRanges([[-28.13, 61.88], [28.13, -50.64]]); }); - - drag([[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]]); - assertPoints([{lon: 20, lat: 20}]); - assertLassoPoints([ - [13.28, 25.97], [13.28, 14.33], [25.71, 14.33], [25.71, 25.97], [13.28, 25.97] - ]); - - return doubleClick(250, 200).then(function() { - expect(selectingCnt).toBe(1, 'plotly_selecting call count'); - expect(selectedCnt).toBe(1, 'plotly_selected call count'); - expect(deselectCnt).toBe(1, 'plotly_deselect call count'); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run([[300, 200], [300, 300], [400, 300], [400, 200], [300, 200]], function() { + assertPoints([[-10, 10], [-20, 20], [-30, 30]]); + assertLassoPoints([ + [-56.25, 61.88], [-56.24, 5.63], [0, 5.63], [0, 61.88], [-56.25, 61.88] + ]); }); }) .then(function() { @@ -626,27 +693,47 @@ describe('select box and lasso', function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) .then(function() { - var selectingCnt = 0; - var selectedCnt = 0; - var deselectCnt = 0; - - gd.once('plotly_selecting', function() { - selectingCnt++; - }); + return _runNoSelect([[370, 120], [500, 200]]); + }) + .catch(fail) + .then(done); + }, LONG_TIMEOUT_INTERVAL); - gd.once('plotly_selected', function() { - selectedCnt++; - }); + it('should work on choropleth traces', function(done) { + var assertPoints = makeAssertPoints(['location', 'z']); + var assertRanges = makeAssertRanges('geo', -0.5); + var assertLassoPoints = makeAssertLassoPoints('geo', -0.5); - gd.once('plotly_deselect', function() { - deselectCnt++; - }); + var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-text')); + fig.layout.width = 870; + fig.layout.height = 450; + fig.layout.dragmode = 'select'; + fig.layout.geo.scope = 'europe'; - return doubleClick(250, 200).then(function() { - expect(selectingCnt).toBe(0, 'plotly_selecting call count'); - expect(selectedCnt).toBe(0, 'plotly_selected call count'); - expect(deselectCnt).toBe(0, 'plotly_deselect call count'); - }); + Plotly.plot(gd, fig) + .then(function() { + return _run([[350, 200], [400, 250]], function() { + assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]); + assertRanges([[-19.11, 63.06], [7.31, 53.72]]); + }, [280, 190]); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'lasso'); + }) + .then(function() { + return _run([[350, 200], [400, 200], [400, 250], [350, 250], [350, 200]], function() { + assertPoints([['GBR', 26.507354205352502], ['IRL', 86.4125147625692]]); + assertLassoPoints([ + [-19.11, 63.06], [5.50, 65.25], [7.31, 53.72], [-12.90, 51.70], [-19.11, 63.06] + ]); + }, [280, 190]); + }) + .then(function() { + // make selection handlers don't get called in 'pan' dragmode + return Plotly.relayout(gd, 'dragmode', 'pan'); + }) + .then(function() { + return _runNoSelect([[370, 120], [500, 200]]); }) .catch(fail) .then(done);