Carga
This commit is contained in:
2025-04-17 00:35:33 -06:00
parent 4977462629
commit 67fc72aed5
1333 changed files with 1077639 additions and 0 deletions

View File

@@ -0,0 +1,315 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
var doc = H.doc;
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, extend = U.extend, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
import './MapNavigationOptionsDefault.js';
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* @private
*/
function stopEvent(e) {
if (e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
e.cancelBubble = true;
}
}
/**
* The MapNavigation handles buttons for navigation in addition to mousewheel
* and doubleclick handlers for chart zooming.
*
* @private
* @class
* @name MapNavigation
*
* @param {Highcharts.Chart} chart
* The Chart instance.
*/
function MapNavigation(chart) {
this.navButtons = [];
this.init(chart);
}
/**
* Initialize function.
*
* @function MapNavigation#init
*
* @param {Highcharts.Chart} chart
* The Chart instance.
*
* @return {void}
*/
MapNavigation.prototype.init = function (chart) {
this.chart = chart;
};
/**
* Update the map navigation with new options. Calling this is the same as
* calling `chart.update({ mapNavigation: {} })`.
*
* @function MapNavigation#update
*
* @param {Highcharts.MapNavigationOptions} [options]
* New options for the map navigation.
*
* @return {void}
*/
MapNavigation.prototype.update = function (options) {
var mapNav = this, chart = this.chart, o = chart.options.mapNavigation, attr, outerHandler = function (e) {
this.handler.call(chart, e);
stopEvent(e); // Stop default click event (#4444)
}, navButtons = mapNav.navButtons;
// Merge in new options in case of update, and register back to chart
// options.
if (options) {
o = chart.options.mapNavigation =
merge(chart.options.mapNavigation, options);
}
// Destroy buttons in case of dynamic update
while (navButtons.length) {
navButtons.pop().destroy();
}
if (pick(o.enableButtons, o.enabled) && !chart.renderer.forExport) {
if (!mapNav.navButtonsGroup) {
mapNav.navButtonsGroup = chart.renderer.g().attr({
zIndex: 4 // #4955, // #8392
}).add();
}
objectEach(o.buttons, function (buttonOptions, n) {
buttonOptions = merge(o.buttonOptions, buttonOptions);
// Presentational
if (!chart.styledMode && buttonOptions.theme) {
attr = buttonOptions.theme;
attr.style = merge(buttonOptions.theme.style, buttonOptions.style // #3203
);
}
var button = chart.renderer
.button(buttonOptions.text || '', 0, 0, outerHandler, attr, void 0, void 0, void 0, n === 'zoomIn' ? 'topbutton' : 'bottombutton')
.addClass('highcharts-map-navigation highcharts-' + {
zoomIn: 'zoom-in',
zoomOut: 'zoom-out'
}[n])
.attr({
width: buttonOptions.width,
height: buttonOptions.height,
title: chart.options.lang[n],
padding: buttonOptions.padding,
zIndex: 5
})
.add(mapNav.navButtonsGroup);
button.handler = buttonOptions.onclick;
// Stop double click event (#4444)
addEvent(button.element, 'dblclick', stopEvent);
navButtons.push(button);
extend(buttonOptions, {
width: button.width,
height: 2 * button.height
});
if (!chart.hasLoaded) {
// Align it after the plotBox is known (#12776)
var unbind_1 = addEvent(chart, 'load', function () {
// #15406: Make sure button hasnt been destroyed
if (button.element) {
button.align(buttonOptions, false, buttonOptions.alignTo);
}
unbind_1();
});
}
else {
button.align(buttonOptions, false, buttonOptions.alignTo);
}
});
// Borrowed from overlapping-datalabels. Consider a shared module.
var isIntersectRect_1 = function (box1, box2) { return !(box2.x >= box1.x + box1.width ||
box2.x + box2.width <= box1.x ||
box2.y >= box1.y + box1.height ||
box2.y + box2.height <= box1.y); };
// Check the mapNavigation buttons collision with exporting button
// and translate the mapNavigation button if they overlap.
var adjustMapNavBtn = function () {
var expBtnBBox = chart.exportingGroup && chart.exportingGroup.getBBox();
if (expBtnBBox) {
var navBtnsBBox = mapNav.navButtonsGroup.getBBox();
// If buttons overlap
if (isIntersectRect_1(expBtnBBox, navBtnsBBox)) {
// Adjust the mapNav buttons' position by translating them
// above or below the exporting button
var aboveExpBtn = -navBtnsBBox.y - navBtnsBBox.height +
expBtnBBox.y - 5, belowExpBtn = expBtnBBox.y + expBtnBBox.height -
navBtnsBBox.y + 5, mapNavVerticalAlign = o.buttonOptions && o.buttonOptions.verticalAlign;
// If bottom aligned and adjusting the mapNav button would
// translate it out of the plotBox, translate it up
// instead of down
mapNav.navButtonsGroup.attr({
translateY: mapNavVerticalAlign === 'bottom' ?
aboveExpBtn :
belowExpBtn
});
}
}
};
if (!chart.hasLoaded) {
// Align it after the plotBox is known (#12776) and after the
// hamburger button's position is known so they don't overlap
// (#15782)
addEvent(chart, 'render', adjustMapNavBtn);
}
}
this.updateEvents(o);
};
/**
* Update events, called internally from the update function. Add new event
* handlers, or unbinds events if disabled.
*
* @function MapNavigation#updateEvents
*
* @param {Highcharts.MapNavigationOptions} options
* Options for map navigation.
*
* @return {void}
*/
MapNavigation.prototype.updateEvents = function (options) {
var chart = this.chart;
// Add the double click event
if (pick(options.enableDoubleClickZoom, options.enabled) ||
options.enableDoubleClickZoomTo) {
this.unbindDblClick = this.unbindDblClick || addEvent(chart.container, 'dblclick', function (e) {
chart.pointer.onContainerDblClick(e);
});
}
else if (this.unbindDblClick) {
// Unbind and set unbinder to undefined
this.unbindDblClick = this.unbindDblClick();
}
// Add the mousewheel event
if (pick(options.enableMouseWheelZoom, options.enabled)) {
this.unbindMouseWheel = this.unbindMouseWheel || addEvent(chart.container, doc.onwheel !== void 0 ? 'wheel' : // Newer Firefox
doc.onmousewheel !== void 0 ? 'mousewheel' :
'DOMMouseScroll', function (e) {
// Prevent scrolling when the pointer is over the element with
// that class, for example anotation popup #12100.
if (!chart.pointer.inClass(e.target, 'highcharts-no-mousewheel')) {
chart.pointer.onContainerMouseWheel(e);
// Issue #5011, returning false from non-jQuery event does
// not prevent default
stopEvent(e);
}
return false;
});
}
else if (this.unbindMouseWheel) {
// Unbind and set unbinder to undefined
this.unbindMouseWheel = this.unbindMouseWheel();
}
};
// Add events to the Chart object itself
extend(Chart.prototype, /** @lends Chart.prototype */ {
/**
* Fit an inner box to an outer. If the inner box overflows left or right,
* align it to the sides of the outer. If it overflows both sides, fit it
* within the outer. This is a pattern that occurs more places in
* Highcharts, perhaps it should be elevated to a common utility function.
*
* @ignore
* @function Highcharts.Chart#fitToBox
*
* @param {Highcharts.BBoxObject} inner
*
* @param {Highcharts.BBoxObject} outer
*
* @return {Highcharts.BBoxObject}
* The inner box
*/
fitToBox: function (inner, outer) {
[['x', 'width'], ['y', 'height']].forEach(function (dim) {
var pos = dim[0], size = dim[1];
if (inner[pos] + inner[size] >
outer[pos] + outer[size]) { // right
// the general size is greater, fit fully to outer
if (inner[size] > outer[size]) {
inner[size] = outer[size];
inner[pos] = outer[pos];
}
else { // align right
inner[pos] = outer[pos] +
outer[size] - inner[size];
}
}
if (inner[size] > outer[size]) {
inner[size] = outer[size];
}
if (inner[pos] < outer[pos]) {
inner[pos] = outer[pos];
}
});
return inner;
},
/**
* Highcharts Maps only. Zoom in or out of the map. See also
* {@link Point#zoomTo}. See {@link Chart#fromLatLonToPoint} for how to get
* the `centerX` and `centerY` parameters for a geographic location.
*
* Deprecated as of v9.3 in favor of [MapView.zoomBy](https://api.highcharts.com/class-reference/Highcharts.MapView#zoomBy).
*
* @deprecated
* @function Highcharts.Chart#mapZoom
*
* @param {number} [howMuch]
* How much to zoom the map. Values less than 1 zooms in. 0.5 zooms
* in to half the current view. 2 zooms to twice the current view. If
* omitted, the zoom is reset.
*
* @param {number} [xProjected]
* The projected x position to keep stationary when zooming, if
* available space.
*
* @param {number} [yProjected]
* The projected y position to keep stationary when zooming, if
* available space.
*
* @param {number} [chartX]
* Keep this chart position stationary if possible. This is used for
* example in mousewheel events, where the area under the mouse
* should be fixed as we zoom in.
*
* @param {number} [chartY]
* Keep this chart position stationary if possible.
*
* @deprecated
*/
mapZoom: function (howMuch, xProjected, yProjected, chartX, chartY) {
if (this.mapView) {
if (isNumber(howMuch)) {
// Compliance, mapView.zoomBy uses different values
howMuch = Math.log(howMuch) / Math.log(0.5);
}
this.mapView.zoomBy(howMuch, isNumber(xProjected) && isNumber(yProjected) ?
this.mapView.projection.inverse([xProjected, yProjected]) :
void 0, isNumber(chartX) && isNumber(chartY) ?
[chartX, chartY] :
void 0);
}
}
});
// Extend the Chart.render method to add zooming and panning
addEvent(Chart, 'beforeRender', function () {
// Render the plus and minus buttons. Doing this before the shapes makes
// getBBox much quicker, at least in Chrome.
this.mapNavigation = new MapNavigation(this);
this.mapNavigation.update();
});
H.MapNavigation = MapNavigation;

View File

@@ -0,0 +1,264 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import D from '../Core/DefaultOptions.js';
import U from '../Core/Utilities.js';
var extend = U.extend;
/* *
*
* Constants
*
* */
/**
* The `mapNavigation` option handles buttons for navigation in addition to
* mousewheel and doubleclick handlers for map zooming.
*
* @product highmaps
* @optionparent mapNavigation
*/
var defaultOptions = {
/**
* General options for the map navigation buttons. Individual options
* can be given from the [mapNavigation.buttons](#mapNavigation.buttons)
* option set.
*
* @sample {highmaps} maps/mapnavigation/button-theme/
* Theming the navigation buttons
*/
buttonOptions: {
/**
* What box to align the buttons to. Possible values are `plotBox`
* and `spacingBox`.
*
* @type {Highcharts.ButtonRelativeToValue}
*/
alignTo: 'plotBox',
/**
* The alignment of the navigation buttons.
*
* @type {Highcharts.AlignValue}
*/
align: 'left',
/**
* The vertical alignment of the buttons. Individual alignment can
* be adjusted by each button's `y` offset.
*
* @type {Highcharts.VerticalAlignValue}
*/
verticalAlign: 'top',
/**
* The X offset of the buttons relative to its `align` setting.
*/
x: 0,
/**
* The width of the map navigation buttons.
*/
width: 18,
/**
* The pixel height of the map navigation buttons.
*/
height: 18,
/**
* Padding for the navigation buttons.
*
* @since 5.0.0
*/
padding: 5,
/**
* Text styles for the map navigation buttons.
*
* @type {Highcharts.CSSObject}
* @default {"fontSize": "15px", "fontWeight": "bold"}
*/
style: {
/** @ignore */
fontSize: '15px',
/** @ignore */
fontWeight: 'bold'
},
/**
* A configuration object for the button theme. The object accepts
* SVG properties like `stroke-width`, `stroke` and `fill`. Tri-state
* button styles are supported by the `states.hover` and `states.select`
* objects.
*
* @sample {highmaps} maps/mapnavigation/button-theme/
* Themed navigation buttons
*
* @type {Highcharts.SVGAttributes}
* @default {"stroke-width": 1, "text-align": "center"}
*/
theme: {
/** @ignore */
'stroke-width': 1,
/** @ignore */
'text-align': 'center'
}
},
/**
* The individual buttons for the map navigation. This usually includes
* the zoom in and zoom out buttons. Properties for each button is
* inherited from
* [mapNavigation.buttonOptions](#mapNavigation.buttonOptions), while
* individual options can be overridden. But default, the `onclick`, `text`
* and `y` options are individual.
*/
buttons: {
/**
* Options for the zoom in button. Properties for the zoom in and zoom
* out buttons are inherited from
* [mapNavigation.buttonOptions](#mapNavigation.buttonOptions), while
* individual options can be overridden. By default, the `onclick`,
* `text` and `y` options are individual.
*
* @extends mapNavigation.buttonOptions
*/
zoomIn: {
// eslint-disable-next-line valid-jsdoc
/**
* Click handler for the button.
*
* @type {Function}
* @default function () { this.mapZoom(0.5); }
*/
onclick: function () {
this.mapZoom(0.5);
},
/**
* The text for the button. The tooltip (title) is a language option
* given by [lang.zoomIn](#lang.zoomIn).
*/
text: '+',
/**
* The position of the zoomIn button relative to the vertical
* alignment.
*/
y: 0
},
/**
* Options for the zoom out button. Properties for the zoom in and
* zoom out buttons are inherited from
* [mapNavigation.buttonOptions](#mapNavigation.buttonOptions), while
* individual options can be overridden. By default, the `onclick`,
* `text` and `y` options are individual.
*
* @extends mapNavigation.buttonOptions
*/
zoomOut: {
// eslint-disable-next-line valid-jsdoc
/**
* Click handler for the button.
*
* @type {Function}
* @default function () { this.mapZoom(2); }
*/
onclick: function () {
this.mapZoom(2);
},
/**
* The text for the button. The tooltip (title) is a language option
* given by [lang.zoomOut](#lang.zoomIn).
*/
text: '-',
/**
* The position of the zoomOut button relative to the vertical
* alignment.
*/
y: 28
}
},
/**
* Whether to enable navigation buttons. By default it inherits the
* [enabled](#mapNavigation.enabled) setting.
*
* @type {boolean}
* @apioption mapNavigation.enableButtons
*/
/**
* Whether to enable map navigation. The default is not to enable
* navigation, as many choropleth maps are simple and don't need it.
* Additionally, when touch zoom and mousewheel zoom is enabled, it breaks
* the default behaviour of these interactions in the website, and the
* implementer should be aware of this.
*
* Individual interactions can be enabled separately, namely buttons,
* multitouch zoom, double click zoom, double click zoom to element and
* mousewheel zoom.
*
* @type {boolean}
* @default false
* @apioption mapNavigation.enabled
*/
/**
* Enables zooming in on an area on double clicking in the map. By default
* it inherits the [enabled](#mapNavigation.enabled) setting.
*
* @type {boolean}
* @apioption mapNavigation.enableDoubleClickZoom
*/
/**
* Whether to zoom in on an area when that area is double clicked.
*
* @sample {highmaps} maps/mapnavigation/doubleclickzoomto/
* Enable double click zoom to
*
* @type {boolean}
* @default false
* @apioption mapNavigation.enableDoubleClickZoomTo
*/
/**
* Enables zooming by mouse wheel. By default it inherits the [enabled](
* #mapNavigation.enabled) setting.
*
* @type {boolean}
* @apioption mapNavigation.enableMouseWheelZoom
*/
/**
* Whether to enable multitouch zooming. Note that if the chart covers the
* viewport, this prevents the user from using multitouch and touchdrag on
* the web page, so you should make sure the user is not trapped inside the
* chart. By default it inherits the [enabled](#mapNavigation.enabled)
* setting.
*
* @type {boolean}
* @apioption mapNavigation.enableTouchZoom
*/
/**
* Sensitivity of mouse wheel or trackpad scrolling. 1 is no sensitivity,
* while with 2, one mousewheel delta will zoom in 50%.
*
* @since 4.2.4
*/
mouseWheelSensitivity: 1.1
// enabled: false,
// enableButtons: null, // inherit from enabled
// enableTouchZoom: null, // inherit from enabled
// enableDoubleClickZoom: null, // inherit from enabled
// enableDoubleClickZoomTo: false
// enableMouseWheelZoom: null, // inherit from enabled
};
/* *
*
* Composition
*
* */
// Add language
extend(D.defaultOptions.lang, {
zoomIn: 'Zoom in',
zoomOut: 'Zoom out'
});
// Set the default map navigation options
D.defaultOptions.mapNavigation = defaultOptions;
/* *
*
* Default Export
*
* */
export default defaultOptions;

View File

@@ -0,0 +1,99 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Pointer from '../Core/Pointer.js';
import U from '../Core/Utilities.js';
var defined = U.defined, extend = U.extend, pick = U.pick, wrap = U.wrap;
/* eslint-disable no-invalid-this */
var normalize = Pointer.prototype.normalize;
var totalWheelDelta = 0;
var totalWheelDeltaTimer;
// Extend the Pointer
extend(Pointer.prototype, {
// Add lon and lat information to pointer events
normalize: function (e, chartPosition) {
var chart = this.chart;
e = normalize.call(this, e, chartPosition);
if (chart && chart.mapView) {
var lonLat = chart.mapView.pixelsToLonLat({
x: e.chartX - chart.plotLeft,
y: e.chartY - chart.plotTop
});
if (lonLat) {
extend(e, lonLat);
}
}
return e;
},
// The event handler for the doubleclick event
onContainerDblClick: function (e) {
var chart = this.chart;
e = this.normalize(e);
if (chart.options.mapNavigation.enableDoubleClickZoomTo) {
if (chart.pointer.inClass(e.target, 'highcharts-tracker') &&
chart.hoverPoint) {
chart.hoverPoint.zoomTo();
}
}
else if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
chart.mapZoom(0.5, void 0, void 0, e.chartX, e.chartY);
}
},
// The event handler for the mouse scroll event
onContainerMouseWheel: function (e) {
var chart = this.chart;
e = this.normalize(e);
// Firefox uses e.deltaY or e.detail, WebKit and IE uses wheelDelta
// try wheelDelta first #15656
var delta = (defined(e.wheelDelta) && -e.wheelDelta / 120) ||
e.deltaY || e.detail;
// Wheel zooming on trackpads have different behaviours in Firefox vs
// WebKit. In Firefox the delta increments in steps by 1, so it is not
// distinguishable from true mouse wheel. Therefore we use this timer
// to avoid trackpad zooming going too fast and out of control. In
// WebKit however, the delta is < 1, so we simply disable animation in
// the `chart.mapZoom` call below.
if (Math.abs(delta) >= 1) {
totalWheelDelta += Math.abs(delta);
if (totalWheelDeltaTimer) {
clearTimeout(totalWheelDeltaTimer);
}
totalWheelDeltaTimer = setTimeout(function () {
totalWheelDelta = 0;
}, 50);
}
if (totalWheelDelta < 10 && chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && chart.mapView) {
chart.mapView.zoomBy((chart.options.mapNavigation.mouseWheelSensitivity -
1) * -delta, void 0, [e.chartX, e.chartY],
// Delta less than 1 indicates stepless/trackpad zooming, avoid
// animation delaying the zoom
Math.abs(delta) < 1 ? false : void 0);
}
}
});
// The pinchType is inferred from mapNavigation options.
wrap(Pointer.prototype, 'zoomOption', function (proceed) {
var mapNavigation = this.chart.options.mapNavigation;
// Pinch status
if (pick(mapNavigation.enableTouchZoom, mapNavigation.enabled)) {
this.chart.options.chart.zooming.pinchType = 'xy';
}
proceed.apply(this, [].slice.call(arguments, 1));
});
// Extend the pinchTranslate method to preserve fixed ratio when zooming
wrap(Pointer.prototype, 'pinchTranslate', function (proceed, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
var xBigger;
proceed.call(this, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
// Keep ratio
if (this.chart.options.chart.type === 'map' && this.hasZoom) {
xBigger = transform.scaleX > transform.scaleY;
this.pinchTranslateDirection(!xBigger, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch, xBigger ? transform.scaleX : transform.scaleY);
}
});

View File

@@ -0,0 +1,80 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import SVGRenderer from '../Core/Renderer/SVG/SVGRenderer.js';
var symbols = SVGRenderer.prototype.symbols;
/* *
*
* Functions
*
* */
/* eslint-disable require-jsdoc, valid-jsdoc */
function bottomButton(x, y, w, h, options) {
var r = (options && options.r) || 0;
return selectiveRoundedRect(x - 1, y - 1, w, h, 0, 0, r, r);
}
/**
* Create symbols for the zoom buttons
* @private
*/
function selectiveRoundedRect(x, y, w, h, rTopLeft, rTopRight, rBottomRight, rBottomLeft) {
return [
['M', x + rTopLeft, y],
// top side
['L', x + w - rTopRight, y],
// top right corner
[
'C',
x + w - rTopRight / 2,
y,
x + w,
y + rTopRight / 2,
x + w,
y + rTopRight
],
// right side
['L', x + w, y + h - rBottomRight],
// bottom right corner
[
'C', x + w, y + h - rBottomRight / 2,
x + w - rBottomRight / 2, y + h,
x + w - rBottomRight, y + h
],
// bottom side
['L', x + rBottomLeft, y + h],
// bottom left corner
[
'C',
x + rBottomLeft / 2,
y + h,
x,
y + h - rBottomLeft / 2,
x,
y + h - rBottomLeft
],
// left side
['L', x, y + rTopLeft],
// top left corner
['C', x, y + rTopLeft / 2, x + rTopLeft / 2, y, x + rTopLeft, y],
['Z']
];
}
function topButton(x, y, w, h, options) {
var r = (options && options.r) || 0;
return selectiveRoundedRect(x - 1, y - 1, w, h, r, r, 0, 0);
}
symbols.bottombutton = bottomButton;
symbols.topbutton = topButton;
/* *
*
* Default Export
*
* */
export default symbols;

View File

@@ -0,0 +1,56 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
// Compute bounds from a path element
var boundsFromPath = function (path) {
var x2 = -Number.MAX_VALUE, x1 = Number.MAX_VALUE, y2 = -Number.MAX_VALUE, y1 = Number.MAX_VALUE, validBounds;
path.forEach(function (seg) {
var x = seg[seg.length - 2], y = seg[seg.length - 1];
if (typeof x === 'number' &&
typeof y === 'number') {
x1 = Math.min(x1, x);
x2 = Math.max(x2, x);
y1 = Math.min(y1, y);
y2 = Math.max(y2, y);
validBounds = true;
}
});
if (validBounds) {
return { x1: x1, y1: y1, x2: x2, y2: y2 };
}
};
/**
* Test for point in polygon. Polygon defined as array of [x,y] points.
* @private
*/
var pointInPolygon = function (point, polygon) {
var i, j, rel1, rel2, c = false, x = point.x, y = point.y;
for (i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
rel1 = polygon[i][1] > y;
rel2 = polygon[j][1] > y;
if (rel1 !== rel2 &&
(x < (polygon[j][0] - polygon[i][0]) * (y - polygon[i][1]) /
(polygon[j][1] - polygon[i][1]) +
polygon[i][0])) {
c = !c;
}
}
return c;
};
/* *
*
* Default Export
*
* */
var MapUtilities = {
boundsFromPath: boundsFromPath,
pointInPolygon: pointInPolygon
};
export default MapUtilities;

View File

@@ -0,0 +1,933 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import defaultOptions from './MapViewOptionsDefault.js';
import defaultInsetsOptions from './MapViewInsetsOptionsDefault.js';
import GeoJSONModule from '../Extensions/GeoJSON.js';
var topo2geo = GeoJSONModule.topo2geo;
import MapChart from '../Core/Chart/MapChart.js';
var maps = MapChart.maps;
import MU from './MapUtilities.js';
var boundsFromPath = MU.boundsFromPath, pointInPolygon = MU.pointInPolygon;
import Projection from './Projection.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, clamp = U.clamp, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength;
/**
* The world size in terms of 10k meters in the Web Mercator projection, to
* match a 256 square tile to zoom level 0.
* @private
*/
var worldSize = 400.979322;
var tileSize = 256;
// Compute the zoom from given bounds and the size of the playing field. Used in
// two places, hence the local function.
var zoomFromBounds = function (b, playingField) {
var width = playingField.width, height = playingField.height, scaleToField = Math.max((b.x2 - b.x1) / (width / tileSize), (b.y2 - b.y1) / (height / tileSize));
return Math.log(worldSize / scaleToField) / Math.log(2);
};
/*
const mergeCollections = <
T extends Array<AnyRecord|undefined>
>(a: T, b: T): T => {
b.forEach((newer, i): void => {
// Only merge by id supported for now. We may consider later to support
// more complex rules like those of `Chart.update` with `oneToOne`, but
// it is probably not needed. Existing insets can be disabled by
// overwriting the `geoBounds` with empty data.
if (newer && isString(newer.id)) {
const older = U.find(
a,
(aItem): boolean => (aItem && aItem.id) === newer.id
);
if (older) {
const aIndex = a.indexOf(older);
a[aIndex] = merge(older, newer);
}
}
});
return a;
};
*/
/**
* The map view handles zooming and centering on the map, and various
* client-side projection capabilities.
*
* On a chart instance, the map view is available as `chart.mapView`.
*
* @class
* @name Highcharts.MapView
*
* @param {Highcharts.Chart} chart
* The Chart instance
* @param {Highcharts.MapViewOptions} options
* MapView options
*/
var MapView = /** @class */ (function () {
function MapView(chart, options) {
var _this = this;
this.insets = [];
this.padding = [0, 0, 0, 0];
this.eventsToUnbind = [];
var recommendedMapView;
var recommendedProjection;
if (!(this instanceof MapViewInset)) {
// Handle the global map and series-level mapData
var geoMaps = __spreadArray([
chart.options.chart.map
], (chart.options.series || []).map(function (s) { return s.mapData; }), true).map(function (mapData) { return _this.getGeoMap(mapData); });
var allGeoBounds_1 = [];
geoMaps.forEach(function (geoMap) {
if (geoMap) {
// Use the first geo map as main
if (!recommendedMapView) {
recommendedMapView = geoMap['hc-recommended-mapview'];
}
// Combine the bounding boxes of all loaded maps
if (geoMap.bbox) {
var _a = geoMap.bbox, x1 = _a[0], y1 = _a[1], x2 = _a[2], y2 = _a[3];
allGeoBounds_1.push({ x1: x1, y1: y1, x2: x2, y2: y2 });
}
}
});
// Get the composite bounds
var geoBounds = (allGeoBounds_1.length &&
MapView.compositeBounds(allGeoBounds_1));
// Provide a best-guess recommended projection if not set in the map
// or in user options
if (geoBounds) {
var x1 = geoBounds.x1, y1 = geoBounds.y1, x2 = geoBounds.x2, y2 = geoBounds.y2;
recommendedProjection = (x2 - x1 > 180 && y2 - y1 > 90) ?
// Wide angle, go for the world view
{
name: 'EqualEarth'
} :
// Narrower angle, use a projection better suited for local
// view
{
name: 'LambertConformalConic',
parallels: [y1, y2],
rotation: [-(x1 + x2) / 2]
};
}
// Register the main geo map (from options.chart.map) if set
this.geoMap = geoMaps[0];
}
this.userOptions = options || {};
var o = merge(defaultOptions, { projection: recommendedProjection }, recommendedMapView, options);
// Merge the inset collections by id, or index if id missing
var recInsets = recommendedMapView && recommendedMapView.insets, optInsets = options && options.insets;
if (recInsets && optInsets) {
o.insets = MapView.mergeInsets(recInsets, optInsets);
}
this.chart = chart;
/**
* The current center of the view in terms of `[longitude, latitude]`.
* @name Highcharts.MapView#center
* @readonly
* @type {LonLatArray}
*/
this.center = o.center;
this.options = o;
this.projection = new Projection(o.projection);
// Initialize with full plot box so we don't have to check for undefined
// every time we use it
this.playingField = chart.plotBox;
/**
* The current zoom level of the view.
* @name Highcharts.MapView#zoom
* @readonly
* @type {number}
*/
this.zoom = o.zoom || 0;
// Create the insets
this.createInsets();
// Initialize and respond to chart size changes
this.eventsToUnbind.push(addEvent(chart, 'afterSetChartSize', function () {
_this.playingField = _this.getField();
if (_this.minZoom === void 0 || // When initializing the chart
_this.minZoom === _this.zoom // When resizing the chart
) {
_this.fitToBounds(void 0, void 0, false);
if (
// Set zoom only when initializing the chart
// (do not overwrite when zooming in/out, #17082)
!_this.chart.hasRendered &&
isNumber(_this.userOptions.zoom)) {
_this.zoom = _this.userOptions.zoom;
}
if (_this.userOptions.center) {
merge(true, _this.center, _this.userOptions.center);
}
}
}));
this.setUpEvents();
}
// Merge two collections of insets by the id
MapView.mergeInsets = function (a, b) {
var toObject = function (insets) {
var ob = {};
insets.forEach(function (inset, i) {
ob[inset && inset.id || "i".concat(i)] = inset;
});
return ob;
};
var insetsObj = merge(toObject(a), toObject(b)), insets = Object
.keys(insetsObj)
.map(function (key) { return insetsObj[key]; });
return insets;
};
// Create MapViewInset instances from insets options
MapView.prototype.createInsets = function () {
var _this = this;
var options = this.options, insets = options.insets;
if (insets) {
insets.forEach(function (item) {
var inset = new MapViewInset(_this, merge(options.insetOptions, item));
_this.insets.push(inset);
});
}
};
/**
* Fit the view to given bounds
*
* @function Highcharts.MapView#fitToBounds
* @param {Object} bounds
* Bounds in terms of projected units given as `{ x1, y1, x2, y2 }`.
* If not set, fit to the bounds of the current data set
* @param {number|string} [padding=0]
* Padding inside the bounds. A number signifies pixels, while a
* percentage string (like `5%`) can be used as a fraction of the
* plot area size.
* @param {boolean} [redraw=true]
* Whether to redraw the chart immediately
* @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
* What animation to use for redraw
*/
MapView.prototype.fitToBounds = function (bounds, padding, redraw, animation) {
if (redraw === void 0) { redraw = true; }
var b = bounds || this.getProjectedBounds();
if (b) {
var pad = pick(padding, bounds ? 0 : this.options.padding), fullField = this.getField(false), padArr = isArray(pad) ? pad : [pad, pad, pad, pad];
this.padding = [
relativeLength(padArr[0], fullField.height),
relativeLength(padArr[1], fullField.width),
relativeLength(padArr[2], fullField.height),
relativeLength(padArr[3], fullField.width)
];
// Apply the playing field, corrected with padding
this.playingField = this.getField();
var zoom = zoomFromBounds(b, this.playingField);
// Reset minZoom when fitting to natural bounds
if (!bounds) {
this.minZoom = zoom;
}
var center = this.projection.inverse([
(b.x2 + b.x1) / 2,
(b.y2 + b.y1) / 2
]);
this.setView(center, zoom, redraw, animation);
}
};
MapView.prototype.getField = function (padded) {
if (padded === void 0) { padded = true; }
var padding = padded ? this.padding : [0, 0, 0, 0];
return {
x: padding[3],
y: padding[0],
width: this.chart.plotWidth - padding[1] - padding[3],
height: this.chart.plotHeight - padding[0] - padding[2]
};
};
MapView.prototype.getGeoMap = function (map) {
if (isString(map)) {
return maps[map];
}
if (isObject(map, true)) {
if (map.type === 'FeatureCollection') {
return map;
}
if (map.type === 'Topology') {
return topo2geo(map);
}
}
};
MapView.prototype.getMapBBox = function () {
var bounds = this.getProjectedBounds(), scale = this.getScale();
if (bounds) {
var padding = this.padding, p1 = this.projectedUnitsToPixels({
x: bounds.x1,
y: bounds.y2
}), width = ((bounds.x2 - bounds.x1) * scale +
padding[1] + padding[3]), height = ((bounds.y2 - bounds.y1) * scale +
padding[0] + padding[2]);
return {
width: width,
height: height,
x: p1.x - padding[3],
y: p1.y - padding[0]
};
}
};
MapView.prototype.getProjectedBounds = function () {
var allBounds = this.chart.series.reduce(function (acc, s) {
var bounds = s.getProjectedBounds && s.getProjectedBounds();
if (bounds &&
s.options.affectsMapView !== false) {
acc.push(bounds);
}
return acc;
}, []);
return this.projection.bounds || MapView.compositeBounds(allBounds);
};
MapView.prototype.getScale = function () {
// A zoom of 0 means the world (360x360 degrees) fits in a 256x256 px
// tile
return (tileSize / worldSize) * Math.pow(2, this.zoom);
};
// Calculate the SVG transform to be applied to series groups
MapView.prototype.getSVGTransform = function () {
var _a = this.playingField, x = _a.x, y = _a.y, width = _a.width, height = _a.height, projectedCenter = this.projection.forward(this.center), flipFactor = this.projection.hasCoordinates ? -1 : 1, scaleX = this.getScale(), scaleY = scaleX * flipFactor, translateX = x + width / 2 - projectedCenter[0] * scaleX, translateY = y + height / 2 - projectedCenter[1] * scaleY;
return { scaleX: scaleX, scaleY: scaleY, translateX: translateX, translateY: translateY };
};
/**
* Convert map coordinates in longitude/latitude to pixels
*
* @function Highcharts.MapView#lonLatToPixels
* @since 10.0.0
* @param {Highcharts.MapLonLatObject} lonLat
* The map coordinates
* @return {Highcharts.PositionObject|undefined}
* The pixel position
*/
MapView.prototype.lonLatToPixels = function (lonLat) {
var pos = this.lonLatToProjectedUnits(lonLat);
if (pos) {
return this.projectedUnitsToPixels(pos);
}
};
/**
* Get projected units from longitude/latitude. Insets are accounted for.
* Returns an object with x and y values corresponding to positions on the
* projected plane.
*
* @requires modules/map
*
* @function Highcharts.MapView#lonLatToProjectedUnits
*
* @since 10.0.0
* @sample maps/series/latlon-to-point/ Find a point from lon/lat
*
* @param {Highcharts.MapLonLatObject} lonLat Coordinates.
*
* @return {Highcharts.ProjectedXY} X and Y coordinates in terms of
* projected values
*/
MapView.prototype.lonLatToProjectedUnits = function (lonLat) {
var chart = this.chart, mapTransforms = chart.mapTransforms;
// Legacy, built-in transforms
if (mapTransforms) {
for (var transform in mapTransforms) {
if (Object.hasOwnProperty.call(mapTransforms, transform) &&
mapTransforms[transform].hitZone) {
var coords = chart.transformFromLatLon(lonLat, mapTransforms[transform]);
if (coords && pointInPolygon(coords, mapTransforms[transform].hitZone.coordinates[0])) {
return coords;
}
}
}
return chart.transformFromLatLon(lonLat, mapTransforms['default'] // eslint-disable-line dot-notation
);
}
// Handle insets
for (var _i = 0, _a = this.insets; _i < _a.length; _i++) {
var inset = _a[_i];
if (inset.options.geoBounds &&
pointInPolygon({ x: lonLat.lon, y: lonLat.lat }, inset.options.geoBounds.coordinates[0])) {
var insetProjectedPoint = inset.projection.forward([lonLat.lon, lonLat.lat]), pxPoint = inset.projectedUnitsToPixels({ x: insetProjectedPoint[0], y: insetProjectedPoint[1] });
return this.pixelsToProjectedUnits(pxPoint);
}
}
var point = this.projection.forward([lonLat.lon, lonLat.lat]);
if (!point.outside) {
return { x: point[0], y: point[1] };
}
};
/**
* Calculate longitude/latitude values for a point or position. Returns an
* object with the numeric properties `lon` and `lat`.
*
* @requires modules/map
*
* @function Highcharts.MapView#projectedUnitsToLonLat
*
* @since 10.0.0
*
* @sample maps/demo/latlon-advanced/ Advanced lat/lon demo
*
* @param {Highcharts.Point|Highcharts.ProjectedXY} point
* A `Point` instance or anything containing `x` and `y` properties
* with numeric values.
*
* @return {Highcharts.MapLonLatObject|undefined} An object with `lat` and
* `lon` properties.
*/
MapView.prototype.projectedUnitsToLonLat = function (point) {
var chart = this.chart, mapTransforms = chart.mapTransforms;
// Legacy, built-in transforms
if (mapTransforms) {
for (var transform in mapTransforms) {
if (Object.hasOwnProperty.call(mapTransforms, transform) &&
mapTransforms[transform].hitZone &&
pointInPolygon(point, mapTransforms[transform].hitZone.coordinates[0])) {
return chart.transformToLatLon(point, mapTransforms[transform]);
}
}
return chart.transformToLatLon(point, mapTransforms['default'] // eslint-disable-line dot-notation
);
}
var pxPoint = this.projectedUnitsToPixels(point);
for (var _i = 0, _a = this.insets; _i < _a.length; _i++) {
var inset = _a[_i];
if (inset.hitZone &&
pointInPolygon(pxPoint, inset.hitZone.coordinates[0])) {
var insetProjectedPoint = inset
.pixelsToProjectedUnits(pxPoint), coordinates_1 = inset.projection.inverse([insetProjectedPoint.x, insetProjectedPoint.y]);
return { lon: coordinates_1[0], lat: coordinates_1[1] };
}
}
var coordinates = this.projection.inverse([point.x, point.y]);
return { lon: coordinates[0], lat: coordinates[1] };
};
MapView.prototype.redraw = function (animation) {
this.chart.series.forEach(function (s) {
if (s.useMapGeometry) {
s.isDirty = true;
}
});
this.chart.redraw(animation);
};
/**
* Set the view to given center and zoom values.
* @function Highcharts.MapView#setView
* @param {Highcharts.LonLatArray|undefined} center
* The center point
* @param {number} zoom
* The zoom level
* @param {boolean} [redraw=true]
* Whether to redraw immediately
* @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
* Animation options for the redraw
*
* @sample maps/mapview/setview
* Set the view programmatically
*/
MapView.prototype.setView = function (center, zoom, redraw, animation) {
if (redraw === void 0) { redraw = true; }
if (center) {
this.center = center;
}
if (typeof zoom === 'number') {
if (typeof this.minZoom === 'number') {
zoom = Math.max(zoom, this.minZoom);
}
if (typeof this.options.maxZoom === 'number') {
zoom = Math.min(zoom, this.options.maxZoom);
}
// Use isNumber to prevent Infinity (#17205)
if (isNumber(zoom)) {
this.zoom = zoom;
}
}
var bounds = this.getProjectedBounds();
if (bounds) {
var projectedCenter = this.projection.forward(this.center), _a = this.playingField, x = _a.x, y = _a.y, width = _a.width, height = _a.height, scale = this.getScale(), bottomLeft = this.projectedUnitsToPixels({
x: bounds.x1,
y: bounds.y1
}), topRight = this.projectedUnitsToPixels({
x: bounds.x2,
y: bounds.y2
}), boundsCenterProjected = [
(bounds.x1 + bounds.x2) / 2,
(bounds.y1 + bounds.y2) / 2
];
// Constrain to data bounds
// Pixel coordinate system is reversed vs projected
var x1 = bottomLeft.x, y1 = topRight.y, x2 = topRight.x, y2 = bottomLeft.y;
// Map smaller than plot area, center it
if (x2 - x1 < width) {
projectedCenter[0] = boundsCenterProjected[0];
// Off west
}
else if (x1 < x && x2 < x + width) {
// Adjust eastwards
projectedCenter[0] += Math.max(x1 - x, x2 - width - x) / scale;
// Off east
}
else if (x2 > x + width && x1 > x) {
// Adjust westwards
projectedCenter[0] += Math.min(x2 - width - x, x1 - x) / scale;
}
// Map smaller than plot area, center it
if (y2 - y1 < height) {
projectedCenter[1] = boundsCenterProjected[1];
// Off north
}
else if (y1 < y && y2 < y + height) {
// Adjust southwards
projectedCenter[1] -= Math.max(y1 - y, y2 - height - y) / scale;
// Off south
}
else if (y2 > y + height && y1 > y) {
// Adjust northwards
projectedCenter[1] -= Math.min(y2 - height - y, y1 - y) / scale;
}
this.center = this.projection.inverse(projectedCenter);
this.insets.forEach(function (inset) {
if (inset.options.field) {
inset.hitZone = inset.getHitZone();
inset.playingField = inset.getField();
}
});
this.render();
}
fireEvent(this, 'afterSetView');
if (redraw) {
this.redraw(animation);
}
};
/**
* Convert projected units to pixel position
*
* @function Highcharts.MapView#projectedUnitsToPixels
* @param {Highcharts.PositionObject} pos
* The position in projected units
* @return {Highcharts.PositionObject} The position in pixels
*/
MapView.prototype.projectedUnitsToPixels = function (pos) {
var scale = this.getScale(), projectedCenter = this.projection.forward(this.center), field = this.playingField, centerPxX = field.x + field.width / 2, centerPxY = field.y + field.height / 2;
var x = centerPxX - scale * (projectedCenter[0] - pos.x);
var y = centerPxY + scale * (projectedCenter[1] - pos.y);
return { x: x, y: y };
};
/**
* Convert pixel position to longitude and latitude.
*
* @function Highcharts.MapView#pixelsToLonLat
* @since 10.0.0
* @param {Highcharts.PositionObject} pos
* The position in pixels
* @return {Highcharts.MapLonLatObject|undefined}
* The map coordinates
*/
MapView.prototype.pixelsToLonLat = function (pos) {
return this.projectedUnitsToLonLat(this.pixelsToProjectedUnits(pos));
};
/**
* Convert pixel position to projected units
*
* @function Highcharts.MapView#pixelsToProjectedUnits
* @param {Highcharts.PositionObject} pos
* The position in pixels
* @return {Highcharts.PositionObject} The position in projected units
*/
MapView.prototype.pixelsToProjectedUnits = function (pos) {
var x = pos.x, y = pos.y, scale = this.getScale(), projectedCenter = this.projection.forward(this.center), field = this.playingField, centerPxX = field.x + field.width / 2, centerPxY = field.y + field.height / 2;
var projectedX = projectedCenter[0] + (x - centerPxX) / scale;
var projectedY = projectedCenter[1] - (y - centerPxY) / scale;
return { x: projectedX, y: projectedY };
};
MapView.prototype.setUpEvents = function () {
var _this = this;
var chart = this.chart;
// Set up panning for maps. In orthographic projections the globe will
// rotate, otherwise adjust the map center.
var mouseDownCenterProjected;
var mouseDownKey;
var mouseDownRotation;
var onPan = function (e) {
var pinchDown = chart.pointer.pinchDown, projection = _this.projection;
var mouseDownX = chart.mouseDownX, mouseDownY = chart.mouseDownY;
if (pinchDown.length === 1) {
mouseDownX = pinchDown[0].chartX;
mouseDownY = pinchDown[0].chartY;
}
if (typeof mouseDownX === 'number' &&
typeof mouseDownY === 'number') {
var key = "".concat(mouseDownX, ",").concat(mouseDownY), _a = e.originalEvent, chartX = _a.chartX, chartY = _a.chartY;
// Reset starting position
if (key !== mouseDownKey) {
mouseDownKey = key;
mouseDownCenterProjected = _this.projection
.forward(_this.center);
mouseDownRotation = (_this.projection.options.rotation || [0, 0]).slice();
}
// Get the natural zoom level of the projection itself when
// zoomed to view the full world
var worldBounds = projection.def && projection.def.bounds, worldZoom = (worldBounds &&
zoomFromBounds(worldBounds, _this.playingField)) || -Infinity;
// Panning rotates the globe
if (projection.options.name === 'Orthographic' &&
// ... but don't rotate if we're loading only a part of the
// world
(_this.minZoom || Infinity) < worldZoom * 1.1) {
// Empirical ratio where the globe rotates roughly the same
// speed as moving the pointer across the center of the
// projection
var ratio = 440 / (_this.getScale() * Math.min(chart.plotWidth, chart.plotHeight));
if (mouseDownRotation) {
var lon = (mouseDownX - chartX) * ratio -
mouseDownRotation[0], lat = clamp(-mouseDownRotation[1] -
(mouseDownY - chartY) * ratio, -80, 80), zoom = _this.zoom;
_this.update({
projection: {
rotation: [-lon, -lat]
}
}, false);
_this.zoom = zoom;
chart.redraw(false);
}
}
else {
var scale = _this.getScale();
var newCenter = _this.projection.inverse([
mouseDownCenterProjected[0] +
(mouseDownX - chartX) / scale,
mouseDownCenterProjected[1] -
(mouseDownY - chartY) / scale
]);
_this.setView(newCenter, void 0, true, false);
}
e.preventDefault();
}
};
addEvent(chart, 'pan', onPan);
addEvent(chart, 'touchpan', onPan);
// Perform the map zoom by selection
addEvent(chart, 'selection', function (evt) {
// Zoom in
if (!evt.resetSelection) {
var x = evt.x - chart.plotLeft;
var y = evt.y - chart.plotTop;
var _a = _this.pixelsToProjectedUnits({ x: x, y: y }), y1 = _a.y, x1 = _a.x;
var _b = _this.pixelsToProjectedUnits({ x: x + evt.width, y: y + evt.height }), y2 = _b.y, x2 = _b.x;
_this.fitToBounds({ x1: x1, y1: y1, x2: x2, y2: y2 }, void 0, true, evt.originalEvent.touches ?
// On touch zoom, don't animate, since we're already in
// transformed zoom preview
false :
// On mouse zoom, obey the chart-level animation
void 0);
// Only for mouse. Touch users can pinch out.
if (!/^touch/.test((evt.originalEvent.type))) {
chart.showResetZoom();
}
evt.preventDefault();
// Reset zoom
}
else {
_this.zoomBy();
}
});
};
MapView.prototype.render = function () {
// We need a group for the insets
if (!this.group) {
this.group = this.chart.renderer.g('map-view')
.attr({ zIndex: 4 })
.add();
}
};
/**
* Update the view with given options
*
* @function Highcharts.MapView#update
*
* @param {Partial<Highcharts.MapViewOptions>} options
* The new map view options to apply
* @param {boolean} [redraw=true]
* Whether to redraw immediately
* @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
* The animation to apply to a the redraw
*/
MapView.prototype.update = function (options, redraw, animation) {
if (redraw === void 0) { redraw = true; }
var newProjection = options.projection;
var isDirtyProjection = newProjection && ((Projection.toString(newProjection) !==
Projection.toString(this.options.projection))), isDirtyInsets = false;
merge(true, this.userOptions, options);
merge(true, this.options, options);
// If anything changed with the insets, destroy them all and create
// again below
if ('insets' in options) {
this.insets.forEach(function (inset) { return inset.destroy(); });
this.insets.length = 0;
isDirtyInsets = true;
}
if (isDirtyProjection || isDirtyInsets) {
this.chart.series.forEach(function (series) {
var groups = series.transformGroups;
if (series.clearBounds) {
series.clearBounds();
}
series.isDirty = true;
series.isDirtyData = true;
// Destroy inset transform groups
if (isDirtyInsets && groups) {
while (groups.length > 1) {
var group = groups.pop();
if (group) {
group.destroy();
}
}
}
});
if (isDirtyProjection) {
this.projection = new Projection(this.options.projection);
}
// Create new insets
if (isDirtyInsets) {
this.createInsets();
}
// Fit to natural bounds if center/zoom are not explicitly given
if (!options.center && !isNumber(options.zoom)) {
this.fitToBounds(void 0, void 0, false);
}
}
if (options.center || isNumber(options.zoom)) {
this.setView(this.options.center, options.zoom, false);
}
if (redraw) {
this.chart.redraw(animation);
}
};
/**
* Zoom the map view by a given number
*
* @function Highcharts.MapView#zoomBy
*
* @param {number|undefined} [howMuch]
* The amount of zoom to apply. 1 zooms in on half the current view,
* -1 zooms out. Pass `undefined` to zoom to the full bounds of the
* map.
* @param {Highcharts.LonLatArray} [coords]
* Optional map coordinates to keep fixed
* @param {Array<number>} [chartCoords]
* Optional chart coordinates to keep fixed, in pixels
* @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
* The animation to apply to a the redraw
*/
MapView.prototype.zoomBy = function (howMuch, coords, chartCoords, animation) {
var chart = this.chart;
var projectedCenter = this.projection.forward(this.center);
// let { x, y } = coords || {};
var _a = coords ? this.projection.forward(coords) : [], x = _a[0], y = _a[1];
if (typeof howMuch === 'number') {
var zoom = this.zoom + howMuch;
var center = void 0;
// Keep chartX and chartY stationary - convert to lat and lng
if (chartCoords) {
var chartX = chartCoords[0], chartY = chartCoords[1];
var scale = this.getScale();
var offsetX = chartX - chart.plotLeft - chart.plotWidth / 2;
var offsetY = chartY - chart.plotTop - chart.plotHeight / 2;
x = projectedCenter[0] + offsetX / scale;
y = projectedCenter[1] + offsetY / scale;
}
// Keep lon and lat stationary by adjusting the center
if (typeof x === 'number' && typeof y === 'number') {
var scale = 1 - Math.pow(2, this.zoom) / Math.pow(2, zoom);
// const projectedCenter = this.projection.forward(this.center);
var offsetX = projectedCenter[0] - x;
var offsetY = projectedCenter[1] - y;
projectedCenter[0] -= offsetX * scale;
projectedCenter[1] += offsetY * scale;
center = this.projection.inverse(projectedCenter);
}
this.setView(center, zoom, void 0, animation);
// Undefined howMuch => reset zoom
}
else {
this.fitToBounds(void 0, void 0, void 0, animation);
}
};
/* *
* Return the composite bounding box of a collection of bounding boxes
*/
MapView.compositeBounds = function (arrayOfBounds) {
if (arrayOfBounds.length) {
return arrayOfBounds
.slice(1)
.reduce(function (acc, cur) {
acc.x1 = Math.min(acc.x1, cur.x1);
acc.y1 = Math.min(acc.y1, cur.y1);
acc.x2 = Math.max(acc.x2, cur.x2);
acc.y2 = Math.max(acc.y2, cur.y2);
return acc;
}, merge(arrayOfBounds[0]));
}
return;
};
return MapView;
}());
// Putting this in the same file due to circular dependency with MapView
var MapViewInset = /** @class */ (function (_super) {
__extends(MapViewInset, _super);
function MapViewInset(mapView, options) {
var _this = _super.call(this, mapView.chart, options) || this;
_this.id = options.id;
_this.mapView = mapView;
_this.options = merge(defaultInsetsOptions, options);
_this.allBounds = [];
if (_this.options.geoBounds) {
// The path in projected units in the map view's main projection.
// This is used for hit testing where the points should render.
var path = mapView.projection.path(_this.options.geoBounds);
_this.geoBoundsProjectedBox = boundsFromPath(path);
_this.geoBoundsProjectedPolygon = path.map(function (segment) { return [
segment[1] || 0,
segment[2] || 0
]; });
}
return _this;
}
// Get the playing field in pixels
MapViewInset.prototype.getField = function (padded) {
if (padded === void 0) { padded = true; }
var hitZone = this.hitZone;
if (hitZone) {
var padding = padded ? this.padding : [0, 0, 0, 0], polygon = hitZone.coordinates[0], xs = polygon.map(function (xy) { return xy[0]; }), ys = polygon.map(function (xy) { return xy[1]; }), x = Math.min.apply(0, xs) + padding[3], x2 = Math.max.apply(0, xs) - padding[1], y = Math.min.apply(0, ys) + padding[0], y2 = Math.max.apply(0, ys) - padding[2];
if (isNumber(x) && isNumber(y)) {
return {
x: x,
y: y,
width: x2 - x,
height: y2 - y
};
}
}
// Fall back to plot area
return _super.prototype.getField.call(this, padded);
};
// Get the hit zone in pixels
MapViewInset.prototype.getHitZone = function () {
var _a = this, chart = _a.chart, mapView = _a.mapView, options = _a.options, coordinates = (options.field || {}).coordinates;
if (coordinates) {
var polygon = coordinates[0];
if (options.units === 'percent') {
var relativeTo_1 = options.relativeTo === 'mapBoundingBox' &&
mapView.getMapBBox() ||
merge(chart.plotBox, { x: 0, y: 0 });
polygon = polygon.map(function (xy) { return [
relativeLength("".concat(xy[0], "%"), relativeTo_1.width, relativeTo_1.x),
relativeLength("".concat(xy[1], "%"), relativeTo_1.height, relativeTo_1.y)
]; });
}
return {
type: 'Polygon',
coordinates: [polygon]
};
}
};
MapViewInset.prototype.getProjectedBounds = function () {
return MapView.compositeBounds(this.allBounds);
};
// Determine whether a point on the main projected plane is inside the
// geoBounds of the inset.
MapViewInset.prototype.isInside = function (point) {
var _a = this, geoBoundsProjectedBox = _a.geoBoundsProjectedBox, geoBoundsProjectedPolygon = _a.geoBoundsProjectedPolygon;
return Boolean(
// First we do a pre-pass to check whether the test point is inside
// the rectangular bounding box of the polygon. This is less
// expensive and will rule out most cases.
geoBoundsProjectedBox &&
point.x >= geoBoundsProjectedBox.x1 &&
point.x <= geoBoundsProjectedBox.x2 &&
point.y >= geoBoundsProjectedBox.y1 &&
point.y <= geoBoundsProjectedBox.y2 &&
// Next, do the more expensive check whether the point is inside the
// polygon itself.
geoBoundsProjectedPolygon &&
pointInPolygon(point, geoBoundsProjectedPolygon));
};
// Render the map view inset with the border path
MapViewInset.prototype.render = function () {
var _a = this, chart = _a.chart, mapView = _a.mapView, options = _a.options, borderPath = options.borderPath || options.field;
if (borderPath && mapView.group) {
var animate = true;
if (!this.border) {
this.border = chart.renderer
.path()
.addClass('highcharts-mapview-inset-border')
.add(mapView.group);
animate = false;
}
if (!chart.styledMode) {
this.border.attr({
stroke: options.borderColor,
'stroke-width': options.borderWidth
});
}
var crisp_1 = Math.round(this.border.strokeWidth()) % 2 / 2, field_1 = (options.relativeTo === 'mapBoundingBox' &&
mapView.getMapBBox()) || mapView.playingField;
var d = (borderPath.coordinates || []).reduce(function (d, lineString) {
return lineString.reduce(function (d, point, i) {
var x = point[0], y = point[1];
if (options.units === 'percent') {
x = chart.plotLeft + relativeLength("".concat(x, "%"), field_1.width, field_1.x);
y = chart.plotTop + relativeLength("".concat(y, "%"), field_1.height, field_1.y);
}
x = Math.floor(x) + crisp_1;
y = Math.floor(y) + crisp_1;
d.push(i === 0 ? ['M', x, y] : ['L', x, y]);
return d;
}, d);
}, []);
// Apply the border path
this.border[animate ? 'animate' : 'attr']({ d: d });
}
};
MapViewInset.prototype.destroy = function () {
if (this.border) {
this.border = this.border.destroy();
}
this.eventsToUnbind.forEach(function (f) { return f(); });
};
// No chart-level events for insets
MapViewInset.prototype.setUpEvents = function () { };
return MapViewInset;
}(MapView));
// Initialize the MapView after initialization, but before firstRender
addEvent(MapChart, 'afterInit', function () {
this.mapView = new MapView(this, this.options.mapView);
});
export default MapView;

View File

@@ -0,0 +1,148 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/**
* Generic options for the placement and appearance of map insets like
* non-contiguous territories.
*
* @since 10.0.0
* @product highmaps
* @optionparent mapView.insetOptions
*/
var defaultOptions = {
/**
* The border color of the insets.
*
* @sample maps/mapview/insetoptions-border
* Inset border options
* @type {Highcharts.ColorType}
*/
borderColor: "#cccccc" /* Palette.neutralColor20 */,
/**
* The pixel border width of the insets.
*
* @sample maps/mapview/insetoptions-border
* Inset border options
*/
borderWidth: 1,
/**
* @ignore-option
*/
center: [0, 0],
/**
* The padding of the insets. Can be either a number of pixels, a percentage
* string, or an array of either. If an array is given, it sets the top,
* right, bottom, left paddings respectively.
*
* @type {number|string|Array<number|string>}
*/
padding: '10%',
/**
* What coordinate system the `field` and `borderPath` should relate to. If
* `plotBox`, they will be fixed to the plot box and responsively move in
* relation to the main map. If `mapBoundingBox`, they will be fixed to the
* map bounding box, which is constant and centered in different chart sizes
* and ratios.
*
* @validvalue ["plotBox", "mapBoundingBox"]
*/
relativeTo: 'mapBoundingBox',
/**
* What units to use for the `field` and `borderPath` geometries. If
* `percent` (default), they relate to the box given in `relativeTo`. If
* `pixels`, they are absolute values.
*
* @validvalue ["percent", "pixels"]
*/
units: 'percent'
};
/**
* The individual MapView insets, typically used for non-contiguous areas of a
* country. Each item inherits from the generic `insetOptions`.
*
* Some of the TopoJSON files of the [Highcharts Map
* Collection](https://code.highcharts.com/mapdata/) include a property called
* `hc-recommended-mapview`, and some of these include insets. In order to
* override the recommended inset options, an inset option with a matching id
* can be applied, and it will be merged into the embedded settings.
*
* @sample maps/mapview/insets-extended
* Extending the embedded insets
* @sample maps/mapview/insets-complete
* Complete inset config from scratch
*
* @extends mapView.insetOptions
* @type Array<Object>
* @product highmaps
* @apioption mapView.insets
*/
/**
* A geometry object of type `MultiLineString` defining the border path of the
* inset in terms of `units`. If undefined, a border is rendered around the
* `field` geometry. It is recommended that the `borderPath` partly follows the
* outline of the `field` in order to make pointer positioning consistent.
*
* @sample maps/mapview/insets-complete
* Complete inset config with `borderPath`
*
* @product highmaps
* @type {Object|undefined}
* @apioption mapView.insets.borderPath
*/
/**
* A geometry object of type `Polygon` defining where in the chart the inset
* should be rendered, in terms of `units` and relative to the `relativeTo`
* setting. If a `borderPath` is omitted, a border is rendered around the field.
* If undefined, the inset is rendered in the full plot area.
*
* @sample maps/mapview/insets-extended
* Border path emitted, field is rendered
*
* @product highmaps
* @type {Object|undefined}
* @apioption mapView.insets.field
*/
/**
* A geometry object of type `Polygon` encircling the shapes that should be
* rendered in the inset, in terms of geographic coordinates. Geometries within
* this geometry are removed from the default map view and rendered in the
* inset.
*
* @sample maps/mapview/insets-complete
* Complete inset config with `geoBounds`
*
* @product highmaps
* @type {Object}
* @apioption mapView.insets.geoBounds
*/
/**
* The id of the inset, used for internal reference.
*
* @sample maps/mapview/insets-extended
* Extending recommended insets by id
*
* @product highmaps
* @type {string}
* @apioption mapView.insets.id
*/
/**
* The projection options for the inset.
*
* @product highmaps
* @type {Object}
* @extends mapView.projection
* @apioption mapView.insets.projection
*/
/* *
*
* Default Export
*
* */
export default defaultOptions;

View File

@@ -0,0 +1,137 @@
/* *
*
* (c) 2010-2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/**
* The `mapView` options control the initial view of the chart, and how
* projection is set up for raw geoJSON maps (beta as of v9.3).
*
* To set the view dynamically after chart generation, see
* [mapView.setView](/class-reference/Highcharts.MapView#setView).
*
* @since 9.3.0
* @product highmaps
* @optionparent mapView
*/
var defaultOptions = {
/**
* The center of the map in terms of longitude and latitude. For
* preprojected maps (like the GeoJSON files in Map Collection v1.x), the
* units are projected x and y units.
*
* @default [0, 0]
* @type {Highcharts.LonLatArray}
*
* @sample {highmaps} maps/mapview/center-zoom Custom view of a world map
* @sample {highmaps} maps/mapview/get-view Report the current view of a
* preprojected map
*/
center: [0, 0],
/**
* Prevents the end user from zooming too far in on the map. See
* [zoom](#mapView.zoom).
*
* @type {number|undefined}
*
* @sample {highmaps} maps/mapview/maxzoom
* Prevent zooming in too far
*/
maxZoom: void 0,
/**
* The padding inside the plot area when auto fitting to the map bounds. A
* number signifies pixels, and a percentage is relative to the plot area
* size.
*
* @sample {highmaps} maps/chart/plotbackgroundcolor-color
* Visible plot area and percentage padding
* @type {number|string|Array<number|string>}
*/
padding: 0,
/**
* The projection options allow applying client side projection to a map
* given in geographic coordinates, typically from TopoJSON or GeoJSON.
*
* @type {Object}
*
* @sample maps/mapview/projection-explorer
* Projection explorer
* @sample maps/demo/topojson-projection
* Orthographic projection
* @sample maps/mapview/projection-custom-proj4js
* Custom UTM projection definition
* @sample maps/mapview/projection-custom-d3geo
* Custom Robinson projection definition
*/
projection: {
/**
* Projection name. Built-in projections are `EqualEarth`,
* `LambertConformalConic`, `Miller`, `Orthographic` and `WebMercator`.
*
* @type {string}
* @sample maps/mapview/projection-explorer
* Projection explorer
* @sample maps/mapview/projection-custom-proj4js
* Custom UTM projection definition
* @sample maps/mapview/projection-custom-d3geo
* Custom Robinson projection definition
* @sample maps/demo/topojson-projection
* Orthographic projection
*/
name: void 0,
/**
* The two standard parallels that define the map layout in conic
* projections, like the LambertConformalConic projection. If only one
* number is given, the second parallel will be the same as the first.
*
* @sample maps/mapview/projection-parallels
* LCC projection with parallels
* @sample maps/mapview/projection-explorer
* Projection explorer
* @type {Array<number>}
*/
parallels: void 0,
/**
* Rotation of the projection in terms of degrees `[lambda, phi,
* gamma]`. When given, a three-axis spherical rotation is be applied
* to the globe prior to the projection.
*
* * `lambda` shifts the longitudes by the given value.
* * `phi` shifts the latitudes by the given value. Can be omitted.
* * `gamma` applies a _roll_. Can be omitted.
*
* @sample maps/mapview/projection-explorer
* Projection explorer
* @sample maps/mapview/projection-america-centric
* America-centric world map
*/
rotation: void 0
},
/**
* The zoom level of a map. Higher zoom levels means more zoomed in. An
* increase of 1 zooms in to a quarter of the viewed area (half the width
* and height). Defaults to fitting to the map bounds.
*
* In a `WebMercator` projection, a zoom level of 0 represents
* the world in a 256x256 pixel square. This is a common concept for WMS
* tiling software.
*
* @type {number|undefined}
* @sample {highmaps} maps/mapview/center-zoom
* Custom view of a world map
* @sample {highmaps} maps/mapview/get-view
* Report the current view of a preprojected map
*/
zoom: void 0
};
/* *
*
* Default Export
*
* */
export default defaultOptions;

View File

@@ -0,0 +1,530 @@
/* *
*
* (c) 2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import PC from '../Core/Geometry/PolygonClip.js';
var clipLineString = PC.clipLineString, clipPolygon = PC.clipPolygon;
import registry from './Projections/ProjectionRegistry.js';
import U from '../Core/Utilities.js';
var clamp = U.clamp, erase = U.erase;
var deg2rad = Math.PI * 2 / 360;
// Safe padding on either side of the antimeridian to avoid points being
// projected to the wrong side of the plane
var floatCorrection = 0.000001;
// Keep longitude within -180 and 180. This is faster than using the modulo
// operator, and preserves the distinction between -180 and 180.
var wrapLon = function (lon) {
// Replacing the if's with while would increase the range, but make it prone
// to crashes on bad data
if (lon < -180) {
lon += 360;
}
if (lon > 180) {
lon -= 360;
}
return lon;
};
var Projection = /** @class */ (function () {
function Projection(options) {
if (options === void 0) { options = {}; }
// Whether the chart has points, lines or polygons given as coordinates
// with positive up, as opposed to paths in the SVG plane with positive
// down.
this.hasCoordinates = false;
// Whether the chart has true projection as opposed to pre-projected geojson
// as in the legacy map collection.
this.hasGeoProjection = false;
this.maxLatitude = 90;
this.options = options;
var name = options.name, projectedBounds = options.projectedBounds, rotation = options.rotation;
this.rotator = rotation ? this.getRotator(rotation) : void 0;
var ProjectionDefinition = name ? Projection.registry[name] : void 0;
if (ProjectionDefinition) {
this.def = new ProjectionDefinition(options);
}
var _a = this, def = _a.def, rotator = _a.rotator;
if (def) {
this.maxLatitude = def.maxLatitude || 90;
this.hasGeoProjection = true;
}
if (rotator && def) {
this.forward = function (lonLat) {
return def.forward(rotator.forward(lonLat));
};
this.inverse = function (xy) {
return rotator.inverse(def.inverse(xy));
};
}
else if (def) {
this.forward = function (lonLat) { return def.forward(lonLat); };
this.inverse = function (xy) { return def.inverse(xy); };
}
else if (rotator) {
this.forward = rotator.forward;
this.inverse = rotator.inverse;
}
// Projected bounds/clipping
this.bounds = projectedBounds === 'world' ?
def && def.bounds :
projectedBounds;
}
// Add a projection definition to the registry, accessible by its `name`.
Projection.add = function (name, definition) {
Projection.registry[name] = definition;
};
// Calculate the great circle between two given coordinates
Projection.greatCircle = function (point1, point2, inclusive) {
var atan2 = Math.atan2, cos = Math.cos, sin = Math.sin, sqrt = Math.sqrt;
var lat1 = point1[1] * deg2rad;
var lon1 = point1[0] * deg2rad;
var lat2 = point2[1] * deg2rad;
var lon2 = point2[0] * deg2rad;
var deltaLat = lat2 - lat1;
var deltaLng = lon2 - lon1;
var calcA = sin(deltaLat / 2) * sin(deltaLat / 2) +
cos(lat1) * cos(lat2) * sin(deltaLng / 2) * sin(deltaLng / 2);
var calcB = 2 * atan2(sqrt(calcA), sqrt(1 - calcA));
var distance = calcB * 6371e3; // in meters
var jumps = Math.round(distance / 500000); // 500 km each jump
var lineString = [];
if (inclusive) {
lineString.push(point1);
}
if (jumps > 1) {
var step = 1 / jumps;
for (var fraction = step; fraction < 0.999; // Account for float errors
fraction += step) {
var A = sin((1 - fraction) * calcB) / sin(calcB);
var B = sin(fraction * calcB) / sin(calcB);
var x = A * cos(lat1) * cos(lon1) + B * cos(lat2) * cos(lon2);
var y = A * cos(lat1) * sin(lon1) + B * cos(lat2) * sin(lon2);
var z = A * sin(lat1) + B * sin(lat2);
var lat3 = atan2(z, sqrt(x * x + y * y));
var lon3 = atan2(y, x);
lineString.push([lon3 / deg2rad, lat3 / deg2rad]);
}
}
if (inclusive) {
lineString.push(point2);
}
return lineString;
};
Projection.insertGreatCircles = function (poly) {
var i = poly.length - 1;
while (i--) {
// Distance in degrees, either in lon or lat. Avoid heavy
// calculation of true distance.
var roughDistance = Math.max(Math.abs(poly[i][0] - poly[i + 1][0]), Math.abs(poly[i][1] - poly[i + 1][1]));
if (roughDistance > 10) {
var greatCircle = Projection.greatCircle(poly[i], poly[i + 1]);
if (greatCircle.length) {
poly.splice.apply(poly, __spreadArray([i + 1, 0], greatCircle, false));
}
}
}
};
Projection.toString = function (options) {
var _a = options || {}, name = _a.name, rotation = _a.rotation;
return [name, rotation && rotation.join(',')].join(';');
};
Projection.prototype.lineIntersectsBounds = function (line) {
var _a = this.bounds || {}, x1 = _a.x1, x2 = _a.x2, y1 = _a.y1, y2 = _a.y2;
var getIntersect = function (line, dim, val) {
var p1 = line[0], p2 = line[1], otherDim = dim ? 0 : 1;
// Check if points are on either side of the line
if (typeof val === 'number' && p1[dim] >= val !== p2[dim] >= val) {
var fraction = ((val - p1[dim]) / (p2[dim] - p1[dim])), crossingVal = p1[otherDim] +
fraction * (p2[otherDim] - p1[otherDim]);
return dim ? [crossingVal, val] : [val, crossingVal];
}
};
var intersection, ret = line[0];
if ((intersection = getIntersect(line, 0, x1))) {
ret = intersection;
// Assuming line[1] was originally outside, replace it with the
// intersection point so that the horizontal intersection will
// be correct.
line[1] = intersection;
}
else if ((intersection = getIntersect(line, 0, x2))) {
ret = intersection;
line[1] = intersection;
}
if ((intersection = getIntersect(line, 1, y1))) {
ret = intersection;
}
else if ((intersection = getIntersect(line, 1, y2))) {
ret = intersection;
}
return ret;
};
/*
* Take the rotation options and return the appropriate projection functions
*/
Projection.prototype.getRotator = function (rotation) {
var deltaLambda = rotation[0] * deg2rad, deltaPhi = (rotation[1] || 0) * deg2rad, deltaGamma = (rotation[2] || 0) * deg2rad;
var cosDeltaPhi = Math.cos(deltaPhi), sinDeltaPhi = Math.sin(deltaPhi), cosDeltaGamma = Math.cos(deltaGamma), sinDeltaGamma = Math.sin(deltaGamma);
if (deltaLambda === 0 && deltaPhi === 0 && deltaGamma === 0) {
// Don't waste processing time
return;
}
return {
forward: function (lonLat) {
// Lambda (lon) rotation
var lon = lonLat[0] * deg2rad + deltaLambda;
// Phi (lat) and gamma rotation
var lat = lonLat[1] * deg2rad, cosLat = Math.cos(lat), x = Math.cos(lon) * cosLat, y = Math.sin(lon) * cosLat, sinLat = Math.sin(lat), k = sinLat * cosDeltaPhi + x * sinDeltaPhi;
return [
Math.atan2(y * cosDeltaGamma - k * sinDeltaGamma, x * cosDeltaPhi - sinLat * sinDeltaPhi) / deg2rad,
Math.asin(k * cosDeltaGamma + y * sinDeltaGamma) / deg2rad
];
},
inverse: function (rLonLat) {
// Lambda (lon) unrotation
var lon = rLonLat[0] * deg2rad;
// Phi (lat) and gamma unrotation
var lat = rLonLat[1] * deg2rad, cosLat = Math.cos(lat), x = Math.cos(lon) * cosLat, y = Math.sin(lon) * cosLat, sinLat = Math.sin(lat), k = sinLat * cosDeltaGamma - y * sinDeltaGamma;
return [
(Math.atan2(y * cosDeltaGamma + sinLat * sinDeltaGamma, x * cosDeltaPhi + k * sinDeltaPhi) - deltaLambda) / deg2rad,
Math.asin(k * cosDeltaPhi - x * sinDeltaPhi) / deg2rad
];
}
};
};
// Project a lonlat coordinate position to xy. Dynamically overridden when
// projection is set.
Projection.prototype.forward = function (lonLat) {
return lonLat;
};
// Unproject an xy chart coordinate position to lonlat. Dynamically
// overridden when projection is set.
Projection.prototype.inverse = function (xy) {
return xy;
};
Projection.prototype.cutOnAntimeridian = function (poly, isPolygon) {
var antimeridian = 180;
var intersections = [];
var polygons = [poly];
poly.forEach(function (lonLat, i) {
var previousLonLat = poly[i - 1];
if (!i) {
if (!isPolygon) {
return;
}
// Else, wrap to beginning
previousLonLat = poly[poly.length - 1];
}
var lon1 = previousLonLat[0], lon2 = lonLat[0];
if (
// Both points, after rotating for antimeridian, are on the far
// side of the Earth
(lon1 < -90 || lon1 > 90) &&
(lon2 < -90 || lon2 > 90) &&
// ... and on either side of the plane
(lon1 > 0) !== (lon2 > 0)) {
// Interpolate to the intersection latitude
var fraction = clamp((antimeridian - (lon1 + 360) % 360) /
((lon2 + 360) % 360 - (lon1 + 360) % 360), 0, 1), lat = (previousLonLat[1] +
fraction * (lonLat[1] - previousLonLat[1]));
intersections.push({
i: i,
lat: lat,
direction: lon1 < 0 ? 1 : -1,
previousLonLat: previousLonLat,
lonLat: lonLat
});
}
});
var polarIntersection;
if (intersections.length) {
if (isPolygon) {
// Simplified use of the even-odd rule, if there is an odd
// amount of intersections between the polygon and the
// antimeridian, the pole is inside the polygon. Applies
// primarily to Antarctica.
if (intersections.length % 2 === 1) {
polarIntersection = intersections.slice().sort(function (a, b) { return Math.abs(b.lat) - Math.abs(a.lat); })[0];
erase(intersections, polarIntersection);
}
// Pull out slices of the polygon that is on the opposite side
// of the antimeridian compared to the starting point
var i = intersections.length - 2;
while (i >= 0) {
var index = intersections[i].i;
var lonPlus = wrapLon(antimeridian +
intersections[i].direction * floatCorrection);
var lonMinus = wrapLon(antimeridian -
intersections[i].direction * floatCorrection);
var slice = poly.splice.apply(poly, __spreadArray([index,
intersections[i + 1].i - index], Projection.greatCircle([lonPlus, intersections[i].lat], [lonPlus, intersections[i + 1].lat], true), false));
// Add interpolated points close to the cut
slice.push.apply(slice, Projection.greatCircle([lonMinus, intersections[i + 1].lat], [lonMinus, intersections[i].lat], true));
polygons.push(slice);
i -= 2;
}
// Insert dummy points close to the pole
if (polarIntersection) {
for (var i_1 = 0; i_1 < polygons.length; i_1++) {
var direction = polarIntersection.direction, lat = polarIntersection.lat, poly_1 = polygons[i_1], indexOf = poly_1.indexOf(polarIntersection.lonLat);
if (indexOf > -1) {
var polarLatitude = (lat < 0 ? -1 : 1) *
this.maxLatitude;
var lon1 = wrapLon(antimeridian +
direction * floatCorrection);
var lon2 = wrapLon(antimeridian -
direction * floatCorrection);
var polarSegment = Projection.greatCircle([lon1, lat], [lon1, polarLatitude], true);
// Circle around the pole point in order to make
// polygon clipping right. Without this, Antarctica
// would wrap the wrong way in an LLC projection
// with parallels [30, 40].
for (var lon = lon1 + 120 * direction; lon > -180 && lon < 180; lon += 120 * direction) {
polarSegment.push([lon, polarLatitude]);
}
polarSegment.push.apply(polarSegment, Projection.greatCircle([lon2, polarLatitude], [lon2, polarIntersection.lat], true));
poly_1.splice.apply(poly_1, __spreadArray([indexOf,
0], polarSegment, false));
break;
}
}
}
// Map lines, not closed
}
else {
var i = intersections.length;
while (i--) {
var index = intersections[i].i;
var slice = poly.splice(index, poly.length,
// Add interpolated point close to the cut
[
wrapLon(antimeridian +
intersections[i].direction * floatCorrection),
intersections[i].lat
]);
// Add interpolated point close to the cut
slice.unshift([
wrapLon(antimeridian -
intersections[i].direction * floatCorrection),
intersections[i].lat
]);
polygons.push(slice);
}
}
}
return polygons;
};
// Take a GeoJSON geometry and return a translated SVGPath
Projection.prototype.path = function (geometry) {
var _this = this;
var _a = this, bounds = _a.bounds, def = _a.def, rotator = _a.rotator;
var antimeridian = 180;
var path = [];
var isPolygon = geometry.type === 'Polygon' ||
geometry.type === 'MultiPolygon';
// @todo: It doesn't really have to do with whether north is
// positive. It depends on whether the coordinates are
// pre-projected.
var hasGeoProjection = this.hasGeoProjection;
// Detect whether we need to do antimeridian cutting and clipping to
// bounds. The alternative (currently for Orthographic) is to apply a
// clip angle.
var projectingToPlane = !def || def.antimeridianCutting !== false;
// We need to rotate in a separate step before applying antimeridian
// cutting
var preclip = projectingToPlane ? rotator : void 0;
var postclip = projectingToPlane ? (def || this) : this;
var boundsPolygon;
if (bounds) {
boundsPolygon = [
[bounds.x1, bounds.y1],
[bounds.x2, bounds.y1],
[bounds.x2, bounds.y2],
[bounds.x1, bounds.y2]
];
}
var addToPath = function (polygon) {
// Create a copy of the original coordinates. The copy applies a
// correction of points close to the antimeridian in order to
// prevent the points to be projected to the wrong side of the
// plane. Float errors in topojson or in the projection may cause
// that.
var poly = polygon.map(function (lonLat) {
if (projectingToPlane) {
if (preclip) {
lonLat = preclip.forward(lonLat);
}
var lon = lonLat[0];
if (Math.abs(lon - antimeridian) < floatCorrection) {
if (lon < antimeridian) {
lon = antimeridian - floatCorrection;
}
else {
lon = antimeridian + floatCorrection;
}
}
lonLat = [lon, lonLat[1]];
}
return lonLat;
});
var polygons = [poly];
if (hasGeoProjection) {
// Insert great circles into long straight lines
Projection.insertGreatCircles(poly);
if (projectingToPlane) {
polygons = _this.cutOnAntimeridian(poly, isPolygon);
}
}
polygons.forEach(function (poly) {
if (poly.length < 2) {
return;
}
var movedTo = false;
var firstValidLonLat;
var lastValidLonLat;
var gap = false;
var pushToPath = function (point) {
if (!movedTo) {
path.push(['M', point[0], point[1]]);
movedTo = true;
}
else {
path.push(['L', point[0], point[1]]);
}
};
var someOutside = false, someInside = false;
var points = poly.map(function (lonLat) {
var xy = postclip.forward(lonLat);
if (xy.outside) {
someOutside = true;
}
else {
someInside = true;
}
// Mercator projects pole points to Infinity, and
// clipPolygon is not able to handle it.
if (xy[1] === Infinity) {
xy[1] = 10e9;
}
else if (xy[1] === -Infinity) {
xy[1] = -10e9;
}
return xy;
});
if (projectingToPlane) {
// Wrap around in order for pointInPolygon to work
if (isPolygon) {
points.push(points[0]);
}
if (someOutside) {
// All points are outside
if (!someInside) {
return;
}
// Some inside, some outside. Clip to the bounds.
if (boundsPolygon) {
// Polygons
if (isPolygon) {
points = clipPolygon(points, boundsPolygon);
// Linestrings
}
else if (bounds) {
clipLineString(points, boundsPolygon)
.forEach(function (points) {
movedTo = false;
points.forEach(pushToPath);
});
return;
}
}
}
points.forEach(pushToPath);
// For orthographic projection, or when a clipAngle applies
}
else {
for (var i = 0; i < points.length; i++) {
var lonLat = poly[i], point = points[i];
if (!point.outside) {
// In order to be able to interpolate if the first
// or last point is invalid (on the far side of the
// globe in an orthographic projection), we need to
// push the first valid point to the end of the
// polygon.
if (isPolygon && !firstValidLonLat) {
firstValidLonLat = lonLat;
poly.push(lonLat);
points.push(point);
}
// When entering the first valid point after a gap
// of invalid points, typically on the far side of
// the globe in an orthographic projection.
if (gap && lastValidLonLat) {
// For areas, in an orthographic projection, the
// great circle between two visible points will
// be close to the horizon. A possible exception
// may be when the two points are on opposite
// sides of the globe. It that poses a problem,
// we may have to rewrite this to use the small
// circle related to the current lon0 and lat0.
if (isPolygon && hasGeoProjection) {
var greatCircle = Projection.greatCircle(lastValidLonLat, lonLat);
greatCircle.forEach(function (lonLat) {
return pushToPath(postclip.forward(lonLat));
});
// For lines, just jump over the gap
}
else {
movedTo = false;
}
}
pushToPath(point);
lastValidLonLat = lonLat;
gap = false;
}
else {
gap = true;
}
}
}
});
};
if (geometry.type === 'LineString') {
addToPath(geometry.coordinates);
}
else if (geometry.type === 'MultiLineString') {
geometry.coordinates.forEach(function (c) { return addToPath(c); });
}
else if (geometry.type === 'Polygon') {
geometry.coordinates.forEach(function (c) { return addToPath(c); });
if (path.length) {
path.push(['Z']);
}
}
else if (geometry.type === 'MultiPolygon') {
geometry.coordinates.forEach(function (polygons) {
polygons.forEach(function (c) { return addToPath(c); });
});
if (path.length) {
path.push(['Z']);
}
}
return path;
};
Projection.registry = registry;
return Projection;
}());
export default Projection;

View File

@@ -0,0 +1,51 @@
/* *
*
* Equal Earth projection, an equal-area projection designed to minimize
* distortion and remain pleasing to the eye.
*
* Invented by Bojan Šavrič, Bernhard Jenny, and Tom Patterson in 2018. It is
* inspired by the widely used Robinson projection.
*
* */
'use strict';
var A1 = 1.340264, A2 = -0.081106, A3 = 0.000893, A4 = 0.003796, M = Math.sqrt(3) / 2.0, scale = 74.03120656864502;
var EqualEarth = /** @class */ (function () {
function EqualEarth() {
this.bounds = {
x1: -200.37508342789243,
x2: 200.37508342789243,
y1: -97.52595454902263,
y2: 97.52595454902263
};
}
EqualEarth.prototype.forward = function (lonLat) {
var d = Math.PI / 180, paramLat = Math.asin(M * Math.sin(lonLat[1] * d)), paramLatSq = paramLat * paramLat, paramLatPow6 = paramLatSq * paramLatSq * paramLatSq;
var x = lonLat[0] * d * Math.cos(paramLat) * scale / (M *
(A1 +
3 * A2 * paramLatSq +
paramLatPow6 * (7 * A3 + 9 * A4 * paramLatSq)));
var y = paramLat * scale * (A1 + A2 * paramLatSq + paramLatPow6 * (A3 + A4 * paramLatSq));
return [x, y];
};
EqualEarth.prototype.inverse = function (xy) {
var x = xy[0] / scale, y = xy[1] / scale, d = 180 / Math.PI, epsilon = 1e-9, iterations = 12;
var paramLat = y, paramLatSq, paramLatPow6, fy, fpy, dlat, i;
for (i = 0; i < iterations; ++i) {
paramLatSq = paramLat * paramLat;
paramLatPow6 = paramLatSq * paramLatSq * paramLatSq;
fy = paramLat * (A1 + A2 * paramLatSq + paramLatPow6 * (A3 + A4 * paramLatSq)) - y;
fpy = A1 + 3 * A2 * paramLatSq + paramLatPow6 * (7 * A3 + 9 * A4 * paramLatSq);
paramLat -= dlat = fy / fpy;
if (Math.abs(dlat) < epsilon) {
break;
}
}
paramLatSq = paramLat * paramLat;
paramLatPow6 = paramLatSq * paramLatSq * paramLatSq;
var lon = d * M * x * (A1 + 3 * A2 * paramLatSq + paramLatPow6 * (7 * A3 + 9 * A4 * paramLatSq)) / Math.cos(paramLat);
var lat = d * Math.asin(Math.sin(paramLat) / M);
return [lon, lat];
};
return EqualEarth;
}());
export default EqualEarth;

View File

@@ -0,0 +1,60 @@
/* *
* Lambert Conformal Conic projection
* */
'use strict';
var sign = Math.sign ||
(function (n) { return (n === 0 ? 0 : n > 0 ? 1 : -1); }), scale = 63.78137, deg2rad = Math.PI / 180, halfPI = Math.PI / 2, eps10 = 1e-6, tany = function (y) { return Math.tan((halfPI + y) / 2); };
var LambertConformalConic = /** @class */ (function () {
function LambertConformalConic(options) {
var _a;
var parallels = (options.parallels || [])
.map(function (n) { return n * deg2rad; }), lat1 = parallels[0] || 0, lat2 = (_a = parallels[1]) !== null && _a !== void 0 ? _a : lat1, cosLat1 = Math.cos(lat1);
if (typeof options.projectedBounds === 'object') {
this.projectedBounds = options.projectedBounds;
}
// Apply the global variables
var n = lat1 === lat2 ?
Math.sin(lat1) :
Math.log(cosLat1 / Math.cos(lat2)) / Math.log(tany(lat2) / tany(lat1));
if (Math.abs(n) < 1e-10) {
n = (sign(n) || 1) * 1e-10;
}
this.n = n;
this.c = cosLat1 * Math.pow(tany(lat1), n) / n;
}
LambertConformalConic.prototype.forward = function (lonLat) {
var lon = lonLat[0] * deg2rad, _a = this, c = _a.c, n = _a.n, projectedBounds = _a.projectedBounds;
var lat = lonLat[1] * deg2rad;
if (c > 0) {
if (lat < -halfPI + eps10) {
lat = -halfPI + eps10;
}
}
else {
if (lat > halfPI - eps10) {
lat = halfPI - eps10;
}
}
var r = c / Math.pow(tany(lat), n), x = r * Math.sin(n * lon) * scale, y = (c - r * Math.cos(n * lon)) * scale, xy = [x, y];
if (projectedBounds && (x < projectedBounds.x1 ||
x > projectedBounds.x2 ||
y < projectedBounds.y1 ||
y > projectedBounds.y2)) {
xy.outside = true;
}
return xy;
};
LambertConformalConic.prototype.inverse = function (xy) {
var x = xy[0] / scale, y = xy[1] / scale, _a = this, c = _a.c, n = _a.n, cy = c - y, rho = sign(n) * Math.sqrt(x * x + cy * cy);
var l = Math.atan2(x, Math.abs(cy)) * sign(cy);
if (cy * n < 0) {
l -= Math.PI * sign(x) * sign(cy);
}
return [
(l / n) / deg2rad,
(2 * Math.atan(Math.pow(c / rho, 1 / n)) - halfPI) / deg2rad
];
};
return LambertConformalConic;
}());
export default LambertConformalConic;

View File

@@ -0,0 +1,29 @@
/* *
* Miller projection
* */
'use strict';
var quarterPI = Math.PI / 4, deg2rad = Math.PI / 180, scale = 63.78137;
var Miller = /** @class */ (function () {
function Miller() {
this.bounds = {
x1: -200.37508342789243,
x2: 200.37508342789243,
y1: -146.91480769173063,
y2: 146.91480769173063
};
}
Miller.prototype.forward = function (lonLat) {
return [
lonLat[0] * deg2rad * scale,
1.25 * scale * Math.log(Math.tan(quarterPI + 0.4 * lonLat[1] * deg2rad))
];
};
Miller.prototype.inverse = function (xy) {
return [
(xy[0] / scale) / deg2rad,
2.5 * (Math.atan(Math.exp(0.8 * (xy[1] / scale))) - quarterPI) / deg2rad
];
};
return Miller;
}());
export default Miller;

View File

@@ -0,0 +1,37 @@
/* *
* Orthographic projection
* */
'use strict';
var deg2rad = Math.PI / 180, scale = 63.78460826781007;
var Orthographic = /** @class */ (function () {
function Orthographic() {
this.antimeridianCutting = false;
this.bounds = {
x1: -scale,
x2: scale,
y1: -scale,
y2: scale
};
}
Orthographic.prototype.forward = function (lonLat) {
var lonDeg = lonLat[0], latDeg = lonLat[1];
var lat = latDeg * deg2rad;
var xy = [
Math.cos(lat) * Math.sin(lonDeg * deg2rad) * scale,
Math.sin(lat) * scale
];
if (lonDeg < -90 || lonDeg > 90) {
xy.outside = true;
}
return xy;
};
Orthographic.prototype.inverse = function (xy) {
var x = xy[0] / scale, y = xy[1] / scale, z = Math.sqrt(x * x + y * y), c = Math.asin(z), cSin = Math.sin(c), cCos = Math.cos(c);
return [
Math.atan2(x * cSin, z * cCos) / deg2rad,
Math.asin(z && y * cSin / z) / deg2rad
];
};
return Orthographic;
}());
export default Orthographic;

View File

@@ -0,0 +1,19 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import LambertConformalConic from './LambertConformalConic.js';
import EqualEarth from './EqualEarth.js';
import Miller from './Miller.js';
import Orthographic from './Orthographic.js';
import WebMercator from './WebMercator.js';
var registry = {
EqualEarth: EqualEarth,
LambertConformalConic: LambertConformalConic,
Miller: Miller,
Orthographic: Orthographic,
WebMercator: WebMercator
};
export default registry;

View File

@@ -0,0 +1,36 @@
/* *
* Web Mercator projection, used for most online map tile services
* */
'use strict';
var maxLatitude = 85.0511287798, // The latitude that defines a square
r = 63.78137, deg2rad = Math.PI / 180;
var WebMercator = /** @class */ (function () {
function WebMercator() {
this.bounds = {
x1: -200.37508342789243,
x2: 200.37508342789243,
y1: -200.3750834278071,
y2: 200.3750834278071
};
this.maxLatitude = maxLatitude;
}
WebMercator.prototype.forward = function (lonLat) {
var sinLat = Math.sin(lonLat[1] * deg2rad);
var xy = [
r * lonLat[0] * deg2rad,
r * Math.log((1 + sinLat) / (1 - sinLat)) / 2
];
if (Math.abs(lonLat[1]) > maxLatitude) {
xy.outside = true;
}
return xy;
};
WebMercator.prototype.inverse = function (xy) {
return [
xy[0] / (r * deg2rad),
(2 * Math.atan(Math.exp(xy[1] / r)) - (Math.PI / 2)) / deg2rad
];
};
return WebMercator;
}());
export default WebMercator;