280 lines
11 KiB
JavaScript
280 lines
11 KiB
JavaScript
/* *
|
|
*
|
|
* (c) 2010-2024 Torstein Honsi
|
|
*
|
|
* License: www.highcharts.com/license
|
|
*
|
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
|
*
|
|
* */
|
|
'use strict';
|
|
import D from '../Core/Defaults.js';
|
|
const { setOptions } = D;
|
|
import H from '../Core/Globals.js';
|
|
const { composed } = H;
|
|
import MapNavigationDefaults from './MapNavigationDefaults.js';
|
|
import MapPointer from './MapPointer.js';
|
|
import MapSymbols from './MapSymbols.js';
|
|
import U from '../Core/Utilities.js';
|
|
const { addEvent, extend, merge, objectEach, pick, pushUnique } = U;
|
|
/* *
|
|
*
|
|
* Functions
|
|
*
|
|
* */
|
|
/**
|
|
* @private
|
|
*/
|
|
function stopEvent(e) {
|
|
if (e) {
|
|
if (e.preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
if (e.stopPropagation) {
|
|
e.stopPropagation();
|
|
}
|
|
e.cancelBubble = true;
|
|
}
|
|
}
|
|
/* *
|
|
*
|
|
* Class
|
|
*
|
|
* */
|
|
/**
|
|
* 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.
|
|
*/
|
|
class MapNavigation {
|
|
/* *
|
|
*
|
|
* Static Functions
|
|
*
|
|
* */
|
|
static compose(MapChartClass, PointerClass, SVGRendererClass) {
|
|
MapPointer.compose(PointerClass);
|
|
MapSymbols.compose(SVGRendererClass);
|
|
if (pushUnique(composed, 'Map.Navigation')) {
|
|
// Extend the Chart.render method to add zooming and panning
|
|
addEvent(MapChartClass, '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();
|
|
});
|
|
setOptions(MapNavigationDefaults);
|
|
}
|
|
}
|
|
/* *
|
|
*
|
|
* Constructor
|
|
*
|
|
* */
|
|
constructor(chart) {
|
|
this.chart = chart;
|
|
this.navButtons = [];
|
|
}
|
|
/* *
|
|
*
|
|
* Functions
|
|
*
|
|
* */
|
|
/**
|
|
* Update the map navigation with new options. Calling this is the same as
|
|
* calling `chart.update({ mapNavigation: {} })`.
|
|
*
|
|
* @function MapNavigation#update
|
|
*
|
|
* @param {Partial<Highcharts.MapNavigationOptions>} [options]
|
|
* New options for the map navigation.
|
|
*/
|
|
update(options) {
|
|
const mapNav = this, chart = mapNav.chart, navButtons = mapNav.navButtons, outerHandler = function (e) {
|
|
this.handler.call(chart, e);
|
|
stopEvent(e); // Stop default click event (#4444)
|
|
};
|
|
let navOptions = chart.options.mapNavigation, attr;
|
|
// Merge in new options in case of update, and register back to chart
|
|
// options.
|
|
if (options) {
|
|
navOptions = chart.options.mapNavigation =
|
|
merge(chart.options.mapNavigation, options);
|
|
}
|
|
// Destroy buttons in case of dynamic update
|
|
while (navButtons.length) {
|
|
navButtons.pop().destroy();
|
|
}
|
|
if (!chart.renderer.forExport &&
|
|
pick(navOptions.enableButtons, navOptions.enabled)) {
|
|
if (!mapNav.navButtonsGroup) {
|
|
mapNav.navButtonsGroup = chart.renderer.g()
|
|
.attr({
|
|
zIndex: 7 // #4955, #8392, #20476
|
|
})
|
|
.add();
|
|
}
|
|
objectEach(navOptions.buttons, (buttonOptions, n) => {
|
|
buttonOptions = merge(navOptions.buttonOptions, buttonOptions);
|
|
// Presentational
|
|
if (!chart.styledMode && buttonOptions.theme) {
|
|
attr = buttonOptions.theme;
|
|
attr.style = merge(buttonOptions.theme.style, buttonOptions.style // #3203
|
|
);
|
|
}
|
|
const { text, width = 0, height = 0, padding = 0 } = buttonOptions;
|
|
const button = chart.renderer
|
|
.button(
|
|
// Display the text from options only if it is not plus
|
|
// or minus
|
|
(text !== '+' && text !== '-' && 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,
|
|
height,
|
|
title: chart.options.lang[n],
|
|
padding: buttonOptions.padding,
|
|
zIndex: 5
|
|
})
|
|
.add(mapNav.navButtonsGroup);
|
|
// Add SVG paths for the default symbols, because the text
|
|
// representation of + and - is not sharp and position is not
|
|
// easy to control.
|
|
if (text === '+' || text === '-') {
|
|
// Mysterious +1 to achieve centering
|
|
const w = width + 1, d = [
|
|
['M', padding + 3, padding + height / 2],
|
|
['L', padding + w - 3, padding + height / 2]
|
|
];
|
|
if (text === '+') {
|
|
d.push(['M', padding + w / 2, padding + 3], ['L', padding + w / 2, padding + height - 3]);
|
|
}
|
|
chart.renderer
|
|
.path(d)
|
|
.addClass('highcharts-button-symbol')
|
|
.attr(chart.styledMode ? {} : {
|
|
stroke: buttonOptions.style?.color,
|
|
'stroke-width': 3,
|
|
'stroke-linecap': 'round'
|
|
})
|
|
.add(button);
|
|
}
|
|
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 || 0)
|
|
});
|
|
if (!chart.hasLoaded) {
|
|
// Align it after the plotBox is known (#12776)
|
|
const unbind = addEvent(chart, 'load', () => {
|
|
// #15406: Make sure button hasnt been destroyed
|
|
if (button.element) {
|
|
button.align(buttonOptions, false, buttonOptions.alignTo);
|
|
}
|
|
unbind();
|
|
});
|
|
}
|
|
else {
|
|
button.align(buttonOptions, false, buttonOptions.alignTo);
|
|
}
|
|
});
|
|
// Borrowed from overlapping-datalabels. Consider a shared module.
|
|
const isIntersectRect = (box1, box2) => !(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.
|
|
const adjustMapNavBtn = function () {
|
|
const expBtnBBox = chart.exportingGroup && chart.exportingGroup.getBBox();
|
|
if (expBtnBBox) {
|
|
const navBtnsBBox = mapNav.navButtonsGroup.getBBox();
|
|
// If buttons overlap
|
|
if (isIntersectRect(expBtnBBox, navBtnsBBox)) {
|
|
// Adjust the mapNav buttons' position by translating
|
|
// them above or below the exporting button
|
|
const aboveExpBtn = -navBtnsBBox.y -
|
|
navBtnsBBox.height + expBtnBBox.y - 5, belowExpBtn = expBtnBBox.y + expBtnBBox.height -
|
|
navBtnsBBox.y + 5, mapNavVerticalAlign = (navOptions.buttonOptions &&
|
|
navOptions.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(navOptions);
|
|
}
|
|
/**
|
|
* Update events, called internally from the update function. Add new event
|
|
* handlers, or unbinds events if disabled.
|
|
*
|
|
* @function MapNavigation#updateEvents
|
|
*
|
|
* @param {Partial<Highcharts.MapNavigationOptions>} options
|
|
* Options for map navigation.
|
|
*/
|
|
updateEvents(options) {
|
|
const 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, 'wheel', 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();
|
|
}
|
|
}
|
|
}
|
|
/* *
|
|
*
|
|
* Default Export
|
|
*
|
|
* */
|
|
export default MapNavigation;
|