highcharts
Version:
JavaScript charting framework
1,460 lines (1,444 loc) • 87.9 kB
JavaScript
/**
* @license Highcharts JS v12.2.0 (2025-04-07)
* @module highcharts/modules/draggable-points
* @requires highcharts
*
* (c) 2009-2025 Torstein Honsi
*
* License: www.highcharts.com/license
*/
import * as __WEBPACK_EXTERNAL_MODULE__highcharts_src_js_8202131d__ from "../highcharts.src.js";
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
;// external ["../highcharts.src.js","default"]
const external_highcharts_src_js_default_namespaceObject = __WEBPACK_EXTERNAL_MODULE__highcharts_src_js_8202131d__["default"];
var external_highcharts_src_js_default_default = /*#__PURE__*/__webpack_require__.n(external_highcharts_src_js_default_namespaceObject);
;// ./code/es-modules/Extensions/DraggablePoints/DragDropUtilities.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* Authors: Øystein Moseng, Torstein Hønsi, Jon A. Nygård
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
const { addEvent } = (external_highcharts_src_js_default_default());
/* *
*
* Functions
*
* */
/**
* Add multiple event listeners with the same handler to the same element.
*
* @private
* @function addEvents
* @param {T} el
* The element or object to add listeners to.
* @param {Array<string>} types
* Array with the event types this handler should apply to.
* @param {Function|Highcharts.EventCallbackFunction<T>} fn
* The function callback to execute when the events are fired.
* @param {Highcharts.EventOptionsObject} [options]
* Event options:
* - `order`: The order the event handler should be called. This opens
* for having one handler be called before another, independent of in
* which order they were added.
* @return {Function}
* A callback function to remove the added events.
* @template T
*/
function addEvents(el, types, fn, options) {
const removeFuncs = types.map((type) => addEvent(el, type, fn, options));
return function () {
for (const fn of removeFuncs) {
fn();
}
};
}
/**
* Utility function to count the number of props in an object.
*
* @private
* @function countProps
*
* @param {Object} obj
* The object to count.
*
* @return {number}
* Number of own properties on the object.
*/
function countProps(obj) {
return Object.keys(obj).length;
}
/**
* Utility function to get the value of the first prop of an object. (Note that
* the order of keys in an object is usually not guaranteed.)
*
* @private
* @function getFirstProp
* @param {Highcharts.Dictionary<T>} obj
* The object to count.
* @return {T}
* Value of the first prop in the object.
* @template T
*/
function getFirstProp(obj) {
for (const p in obj) {
if (Object.hasOwnProperty.call(obj, p)) {
return obj[p];
}
}
}
/**
* Take a mouse/touch event and return the event object with chartX/chartY.
*
* @private
* @function getNormalizedEvent
* @param {global.PointerEvent} e
* The event to normalize.
* @param {Highcharts.Chart} chart
* The related chart.
* @return {Highcharts.PointerEventLObject}
* The normalized event.
*/
function getNormalizedEvent(e, chart) {
return (typeof e.chartX === 'undefined' ||
typeof e.chartY === 'undefined' ?
chart.pointer?.normalize(e) || e :
e);
}
/* *
*
* Default Export
*
* */
const DragDropUtilities = {
addEvents,
countProps,
getFirstProp,
getNormalizedEvent
};
/* harmony default export */ const DraggablePoints_DragDropUtilities = (DragDropUtilities);
;// ./code/es-modules/Extensions/DraggablePoints/DragDropDefaults.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* Authors: Øystein Moseng, Torstein Hønsi, Jon A. Nygård
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* *
*
* API Options
*
* */
/**
* The draggable-points module allows points to be moved around or modified in
* the chart. In addition to the options mentioned under the `dragDrop` API
* structure, the module fires three events,
* [point.dragStart](plotOptions.series.point.events.dragStart),
* [point.drag](plotOptions.series.point.events.drag) and
* [point.drop](plotOptions.series.point.events.drop).
*
* @sample {highcharts|highstock}
* highcharts/dragdrop/resize-column
* Draggable column and line series
* @sample {highcharts|highstock}
* highcharts/dragdrop/bar-series
* Draggable bar
* @sample {highcharts|highstock}
* highcharts/dragdrop/drag-bubble
* Draggable bubbles
* @sample {highcharts|highstock}
* highcharts/dragdrop/drag-xrange
* Draggable X range series
* @sample {highcharts|highstock}
* highcharts/dragdrop/undraggable-points
* Dragging disabled for specific points
* @sample {highmaps}
* maps/series/draggable-mappoint
* Draggable Map Point series
*
* @declare Highcharts.SeriesDragDropOptionsObject
* @since 6.2.0
* @requires modules/draggable-points
* @optionparent plotOptions.series.dragDrop
*/
const DragDropDefaults = {
/**
* Set the minimum X value the points can be moved to.
*
* @sample {gantt} gantt/dragdrop/drag-gantt
* Limit dragging
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Limit dragging
*
* @type {number|string}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.dragMinX
*/
/**
* Set the maximum X value the points can be moved to.
*
* @sample {gantt} gantt/dragdrop/drag-gantt
* Limit dragging
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Limit dragging
*
* @type {number|string}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.dragMaxX
*/
/**
* Set the minimum Y value the points can be moved to.
*
* @sample {gantt} gantt/dragdrop/drag-gantt
* Limit dragging
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Limit dragging
*
* @type {number}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.dragMinY
*/
/**
* Set the maximum Y value the points can be moved to.
*
* @sample {gantt} gantt/dragdrop/drag-gantt
* Limit dragging
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Limit dragging
*
* @type {number}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.dragMaxY
*/
/**
* The X precision value to drag to for this series. Set to 0 to disable. By
* default this is disabled, except for category axes, where the default is
* `1`.
*
* @type {number}
* @default 0
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.dragPrecisionX
*/
/**
* The Y precision value to drag to for this series. Set to 0 to disable. By
* default this is disabled, except for category axes, where the default is
* `1`.
*
* @type {number}
* @default 0
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.dragPrecisionY
*/
/**
* Enable dragging in the X dimension.
*
* @type {boolean}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.draggableX
*/
/**
* Enable dragging in the Y dimension. Note that this is not supported for
* TreeGrid axes (the default axis type in Gantt charts).
*
* @type {boolean}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.draggableY
*/
/**
* Group the points by a property. Points with the same property value will
* be grouped together when moving.
*
* @sample {gantt} gantt/dragdrop/drag-gantt
* Drag grouped points
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Drag grouped points
*
* @type {string}
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.groupBy
*/
/**
* Update points as they are dragged. If false, a guide box is drawn to
* illustrate the new point size.
*
* @sample {gantt} gantt/dragdrop/drag-gantt
* liveRedraw disabled
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* liveRedraw disabled
*
* @type {boolean}
* @default true
* @since 6.2.0
* @apioption plotOptions.series.dragDrop.liveRedraw
*/
/**
* Set a key to hold when dragging to zoom the chart. This is useful to
* avoid zooming while moving points. Should be set different than
* [chart.panKey](#chart.panKey).
*
* @type {string}
* @since 6.2.0
* @validvalue ["alt", "ctrl", "meta", "shift"]
* @deprecated
* @requires modules/draggable-points
* @apioption chart.zoomKey
*/
/**
* Callback that fires when starting to drag a point. The mouse event object
* is passed in as an argument. If a drag handle is used, `e.updateProp` is
* set to the data property being dragged. The `this` context is the point.
* See [drag and drop options](plotOptions.series.dragDrop).
*
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Drag events
*
* @type {Highcharts.PointDragStartCallbackFunction}
* @since 6.2.0
* @requires modules/draggable-points
* @apioption plotOptions.series.point.events.dragStart
*/
/**
* Callback that fires while dragging a point. The mouse event is passed in
* as parameter. The original data can be accessed from `e.origin`, and the
* new point values can be accessed from `e.newPoints`. If there is only a
* single point being updated, it can be accessed from `e.newPoint` for
* simplicity, and its ID can be accessed from `e.newPointId`. The `this`
* context is the point being dragged. To stop the default drag action,
* return false. See [drag and drop options](plotOptions.series.dragDrop).
*
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Drag events
* @sample {highcharts|highstock} highcharts/dragdrop/undraggable-points
* Dragging disabled for specific points
*
* @type {Highcharts.PointDragCallbackFunction}
* @since 6.2.0
* @requires modules/draggable-points
* @apioption plotOptions.series.point.events.drag
*/
/**
* Callback that fires when the point is dropped. The parameters passed are
* the same as for [drag](#plotOptions.series.point.events.drag). To stop
* the default drop action, return false. See
* [drag and drop options](plotOptions.series.dragDrop).
*
* @sample {highcharts} highcharts/dragdrop/drag-xrange
* Drag events
* @sample {highcharts|highstock} highcharts/dragdrop/undraggable-points
* Dragging disabled for specific points
*
* @type {Highcharts.PointDropCallbackFunction}
* @since 6.2.0
* @requires modules/draggable-points
* @apioption plotOptions.series.point.events.drop
*/
/**
* Point specific options for the draggable-points module. Overrides options
* on `series.dragDrop`.
*
* @declare Highcharts.SeriesLineDataDragDropOptions
* @extends plotOptions.series.dragDrop
* @since 6.2.0
* @requires modules/draggable-points
* @apioption series.line.data.dragDrop
*/
/**
* The amount of pixels to drag the pointer before it counts as a drag
* operation. This prevents drag/drop to fire when just clicking or
* selecting points.
*
* @type {number}
* @default 2
* @since 6.2.0
*/
dragSensitivity: 2,
/**
* Options for the drag handles available in column series.
*
* @declare Highcharts.DragDropHandleOptionsObject
* @since 6.2.0
* @optionparent plotOptions.column.dragDrop.dragHandle
*/
dragHandle: {
/**
* Function to define the SVG path to use for the drag handles. Takes
* the point as argument. Should return an SVG path in array format. The
* SVG path is automatically positioned on the point.
*
* @type {Function}
* @since 6.2.0
* @apioption plotOptions.column.dragDrop.dragHandle.pathFormatter
*/
// pathFormatter: null,
/**
* The mouse cursor to use for the drag handles. By default this is
* intelligently switching between `ew-resize` and `ns-resize` depending on
* the direction the point is being dragged.
*
* @type {string}
* @since 6.2.0
* @apioption plotOptions.column.dragDrop.dragHandle.cursor
*/
// cursor: null,
/**
* The class name of the drag handles. Defaults to `highcharts-drag-handle`.
*
* @since 6.2.0
*/
className: 'highcharts-drag-handle',
/**
* The fill color of the drag handles.
*
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
* @since 6.2.0
*/
color: '#fff',
/**
* The line color of the drag handles.
*
* @type {Highcharts.ColorString}
* @since 6.2.0
*/
lineColor: 'rgba(0, 0, 0, 0.6)',
/**
* The line width for the drag handles.
*
* @since 6.2.0
*/
lineWidth: 1,
/**
* The z index for the drag handles.
*
* @since 6.2.0
*/
zIndex: 901
},
/**
* Style options for the guide box. The guide box has one state by default,
* the `default` state.
*
* @declare Highcharts.PlotOptionsSeriesDragDropGuideBoxOptions
* @since 6.2.0
* @type {Highcharts.Dictionary<Highcharts.DragDropGuideBoxOptionsObject>}
*/
guideBox: {
/**
* Style options for the guide box default state.
*
* @declare Highcharts.DragDropGuideBoxOptionsObject
* @since 6.2.0
*/
'default': {
/**
* CSS class name of the guide box in this state. Defaults to
* `highcharts-drag-box-default`.
*
* @since 6.2.0
*/
className: 'highcharts-drag-box-default',
/**
* Width of the line around the guide box.
*
* @since 6.2.0
*/
lineWidth: 1,
/**
* Color of the border around the guide box.
*
* @type {Highcharts.ColorString}
* @since 6.2.0
*/
lineColor: '#888',
/**
* Guide box fill color.
*
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
* @since 6.2.0
*/
color: 'rgba(0, 0, 0, 0.1)',
/**
* Guide box cursor.
*
* @since 6.2.0
*/
cursor: 'move',
/**
* Guide box zIndex.
*
* @since 6.2.0
*/
zIndex: 900
}
}
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const DraggablePoints_DragDropDefaults = (DragDropDefaults);
;// ./code/es-modules/Extensions/DraggablePoints/DraggableChart.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* Authors: Øystein Moseng, Torstein Hønsi, Jon A. Nygård
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
const { animObject } = (external_highcharts_src_js_default_default());
const { addEvents: DraggableChart_addEvents, countProps: DraggableChart_countProps, getFirstProp: DraggableChart_getFirstProp, getNormalizedEvent: DraggableChart_getNormalizedEvent } = DraggablePoints_DragDropUtilities;
const { doc } = (external_highcharts_src_js_default_default());
const { addEvent: DraggableChart_addEvent, isArray, merge, pick } = (external_highcharts_src_js_default_default());
/* *
*
* Functions
*
* */
/**
* Add events to document and chart if the chart is draggable.
*
* @private
* @function addDragDropEvents
* @param {Highcharts.Chart} chart
* The chart to add events to.
*/
function addDragDropEvents(chart) {
const container = chart.container;
// Only enable if we have a draggable chart
if (isChartDraggable(chart)) {
DraggableChart_addEvents(container, ['mousedown', 'touchstart'], (e) => {
mouseDown(DraggableChart_getNormalizedEvent(e, chart), chart);
});
DraggableChart_addEvents(container, ['mousemove', 'touchmove'], (e) => {
mouseMove(DraggableChart_getNormalizedEvent(e, chart), chart);
}, {
passive: false
});
DraggableChart_addEvent(container, 'mouseleave', (e) => {
mouseUp(DraggableChart_getNormalizedEvent(e, chart), chart);
});
chart.unbindDragDropMouseUp = DraggableChart_addEvents(doc, ['mouseup', 'touchend'], (e) => {
mouseUp(DraggableChart_getNormalizedEvent(e, chart), chart);
}, {
passive: false
});
// Add flag to avoid doing this again
chart.hasAddedDragDropEvents = true;
// Add cleanup to make sure we don't pollute document
DraggableChart_addEvent(chart, 'destroy', () => {
if (chart.unbindDragDropMouseUp) {
chart.unbindDragDropMouseUp();
}
});
}
}
/**
* Remove the chart's drag handles if they exist.
*
* @private
* @function Highcharts.Chart#hideDragHandles
*/
function chartHideDragHandles() {
const chart = this, dragHandles = (chart.dragHandles || {});
if (dragHandles) {
for (const key of Object.keys(dragHandles)) {
if (dragHandles[key].destroy) {
dragHandles[key].destroy();
}
}
delete chart.dragHandles;
}
}
/**
* Set the state of the guide box.
*
* @private
* @function Highcharts.Chart#setGuideBoxState
* @param {string} state
* The state to set the guide box to.
* @param {Highcharts.Dictionary<Highcharts.DragDropGuideBoxOptionsObject>} [options]
* Additional overall guideBox options to consider.
* @return {Highcharts.SVGElement}
* The modified guide box.
*/
function chartSetGuideBoxState(state, options) {
const guideBox = this.dragGuideBox, guideBoxOptions = merge(DraggablePoints_DragDropDefaults.guideBox, options), stateOptions = merge(guideBoxOptions['default'], // eslint-disable-line dot-notation
guideBoxOptions[state]);
return guideBox
.attr({
'class': stateOptions.className,
stroke: stateOptions.lineColor,
strokeWidth: stateOptions.lineWidth,
fill: stateOptions.color,
cursor: stateOptions.cursor,
zIndex: stateOptions.zIndex
})
// Use pointerEvents 'none' to avoid capturing the click event
.css({ pointerEvents: 'none' });
}
/**
* Check whether the zoomKey or panKey is pressed.
*
* @private
* @function zoomOrPanKeyPressed
* @param {global.Event} e
* A mouse event.
* @return {boolean}
* True if the zoom or pan keys are pressed. False otherwise.
*/
function chartZoomOrPanKeyPressed(e) {
// Check whether the panKey and zoomKey are set in chart.userOptions
const chart = this, chartOptions = chart.options.chart || {}, panKey = chartOptions.panKey && chartOptions.panKey + 'Key', zoomKey = chart.zooming.key && chart.zooming.key + 'Key';
return (e[zoomKey] || e[panKey]);
}
/**
* Composes the chart class with essential functions to support draggable
* points.
*
* @private
* @function compose
*
* @param {Highcharts.Chart} ChartClass
* Class constructor of chart.
*/
function compose(ChartClass) {
const chartProto = ChartClass.prototype;
if (!chartProto.hideDragHandles) {
chartProto.hideDragHandles = chartHideDragHandles;
chartProto.setGuideBoxState = chartSetGuideBoxState;
chartProto.zoomOrPanKeyPressed = chartZoomOrPanKeyPressed;
DraggableChart_addEvent(ChartClass, 'render', onChartRender);
}
}
/**
* Default mouse move handler while dragging. Handles updating points or guide
* box.
*
* @private
* @function dragMove
* @param {Highcharts.PointerEventObject} e
* The mouse move event.
* @param {Highcharts.Point} point
* The point that is dragged.
*/
function dragMove(e, point) {
const series = point.series, chart = series.chart, data = chart.dragDropData, options = merge(series.options.dragDrop, point.options.dragDrop), draggableX = options.draggableX, draggableY = options.draggableY, origin = data.origin, updateProp = data.updateProp;
let dX = e.chartX - origin.chartX, dY = e.chartY - origin.chartY;
const oldDx = dX;
// Handle inverted
if (chart.inverted) {
dX = -dY;
dY = -oldDx;
}
// If we have liveRedraw enabled, update the points immediately. Otherwise
// update the guideBox.
if (pick(options.liveRedraw, true)) {
updatePoints(chart, false);
// Update drag handles
point.showDragHandles();
}
else {
// No live redraw, update guide box
if (updateProp) {
// We are resizing, so resize the guide box
resizeGuideBox(point, dX, dY);
}
else {
// We are moving, so move the guide box
chart.dragGuideBox.translate(draggableX ? dX : 0, draggableY ? dY : 0);
}
}
// Update stored previous dX/Y
origin.prevdX = dX;
origin.prevdY = dY;
}
/**
* Flip a side property, used with resizeRect. If input side is "left", return
* "right" etc.
*
* @private
* @function flipResizeSide
*
* @param {string} side
* Side prop to flip. Can be `left`, `right`, `top` or `bottom`.
*
* @return {"bottom"|"left"|"right"|"top"|undefined}
* The flipped side.
*/
function flipResizeSide(side) {
return {
left: 'right',
right: 'left',
top: 'bottom',
bottom: 'top'
}[side];
}
/**
* Get a list of points that are grouped with this point. If only one point is
* in the group, that point is returned by itself in an array.
*
* @private
* @function getGroupedPoints
* @param {Highcharts.Point} point
* Point to find group from.
* @return {Array<Highcharts.Point>}
* Array of points in this group.
*/
function getGroupedPoints(point) {
const series = point.series, data = series.options.data || [], groupKey = series.options.dragDrop.groupBy;
let points = [];
if (series.boosted && isArray(data)) { // #11156
for (let i = 0, iEnd = data.length; i < iEnd; ++i) {
points.push(new series.pointClass(// eslint-disable-line new-cap
series, data[i]));
points[points.length - 1].index = i;
}
}
else {
points = series.points;
}
return point.options[groupKey] ?
// If we have a grouping option, filter the points by that
points.filter((comparePoint) => (comparePoint.options[groupKey] ===
point.options[groupKey])) :
// Otherwise return the point by itself only
[point];
}
/**
* Calculate new point options from points being dragged.
*
* @private
* @function getNewPoints
*
* @param {Object} dragDropData
* A chart's dragDropData with drag/drop origin information, and info on
* which points are being dragged.
*
* @param {Highcharts.PointerEventObject} newPos
* Event with the new position of the mouse (chartX/Y properties).
*
* @return {Highchats.Dictionary<object>}
* Hashmap with point.id mapped to an object with the original point
* reference, as well as the new data values.
*/
function getNewPoints(dragDropData, newPos) {
const point = dragDropData.point, series = point.series, chart = series.chart, options = merge(series.options.dragDrop, point.options.dragDrop), updateProps = {}, resizeProp = dragDropData.updateProp, hashmap = {}, dragDropProps = point.series.dragDropProps;
// Go through the data props that can be updated on this series and find out
// which ones we want to update.
// eslint-disable-next-line guard-for-in
for (const key in dragDropProps) {
const val = dragDropProps[key];
// If we are resizing, skip if this key is not the correct one or it
// is not resizable.
if (resizeProp && (resizeProp !== key ||
!val.resize ||
val.optionName && options[val.optionName] === false)) {
continue;
}
// If we are resizing, we now know it is good. If we are moving, check
// that moving along this axis is enabled, and the prop is movable.
// If this prop is enabled, add it to be updated.
if (resizeProp || (val.move &&
(val.axis === 'x' && options.draggableX ||
val.axis === 'y' && options.draggableY))) {
if (chart.mapView) {
updateProps[key === 'x' ? 'lon' : 'lat'] = val;
}
else {
updateProps[key] = val;
}
}
}
// Go through the points to be updated and get new options for each of them
for (const p of
// If resizing).forEach(only update the point we are resizing
resizeProp ?
[point] :
dragDropData.groupedPoints) {
hashmap[p.id] = {
point: p,
newValues: p.getDropValues(dragDropData.origin, newPos, updateProps)
};
}
return hashmap;
}
/**
* Get a snapshot of points, mouse position, and guide box dimensions
*
* @private
* @function getPositionSnapshot
*
* @param {Highcharts.PointerEventObject} e
* Mouse event with mouse position to snapshot.
*
* @param {Array<Highcharts.Point>} points
* Points to take snapshot of. We store the value of the data properties
* defined in each series' dragDropProps.
*
* @param {Highcharts.SVGElement} [guideBox]
* The guide box to take snapshot of.
*
* @return {Object}
* Snapshot object. Point properties are placed in a hashmap with IDs as
* keys.
*/
function getPositionSnapshot(e, points, guideBox) {
const res = {
chartX: e.chartX,
chartY: e.chartY,
guideBox: guideBox && {
x: guideBox.attr('x'),
y: guideBox.attr('y'),
width: guideBox.attr('width'),
height: guideBox.attr('height')
},
points: {}
};
// Loop over the points and add their props
for (const point of points) {
const dragDropProps = point.series.dragDropProps || {}, pointProps = {};
// Add all of the props defined in the series' dragDropProps to the
// snapshot
for (const key of Object.keys(dragDropProps)) {
const val = dragDropProps[key], axis = point.series[val.axis + 'Axis'];
pointProps[key] = point[key];
// Record how far cursor was from the point when drag started.
// This later will be used to calculate new value according to the
// current position of the cursor.
// e.g. `high` value is translated to `highOffset`
if (point.series.chart.mapView && point.plotX && point.plotY) {
pointProps[key + 'Offset'] = key === 'x' ?
point.plotX : point.plotY;
}
else {
pointProps[key + 'Offset'] =
// E.g. yAxis.toPixels(point.high), xAxis.toPixels
// (point.end)
axis.toPixels(point[key]) -
(axis.horiz ? e.chartX : e.chartY);
}
}
pointProps.point = point; // Store reference to point
res.points[point.id] = pointProps;
}
return res;
}
/**
* In mousemove events, check that we have dragged mouse further than the
* dragSensitivity before we call mouseMove handler.
*
* @private
* @function hasDraggedPastSensitivity
*
* @param {Highcharts.PointerEventObject} e
* Mouse move event to test.
*
* @param {Highcharts.Chart} chart
* Chart that has started dragging.
*
* @param {number} sensitivity
* Pixel sensitivity to test against.
*
* @return {boolean}
* True if the event is moved past sensitivity relative to the chart's
* drag origin.
*/
function hasDraggedPastSensitivity(e, chart, sensitivity) {
const orig = chart.dragDropData.origin, oldX = orig.chartX, oldY = orig.chartY, newX = e.chartX, newY = e.chartY, distance = Math.sqrt((newX - oldX) * (newX - oldX) +
(newY - oldY) * (newY - oldY));
return distance > sensitivity;
}
/**
* Prepare chart.dragDropData with origin info, and show the guide box.
*
* @private
* @function initDragDrop
* @param {Highcharts.PointerEventObject} e
* Mouse event with original mouse position.
* @param {Highcharts.Point} point
* The point the dragging started on.
* @return {void}
*/
function initDragDrop(e, point) {
const groupedPoints = getGroupedPoints(point), series = point.series, chart = series.chart;
let guideBox;
// If liveRedraw is disabled, show the guide box with the default state
if (!pick(series.options.dragDrop && series.options.dragDrop.liveRedraw, true)) {
chart.dragGuideBox = guideBox = series.getGuideBox(groupedPoints);
chart
.setGuideBoxState('default', series.options.dragDrop.guideBox)
.add(series.group);
}
// Store some data on the chart to pick up later
chart.dragDropData = {
origin: getPositionSnapshot(e, groupedPoints, guideBox),
point: point,
groupedPoints: groupedPoints,
isDragging: true
};
}
/**
* Utility function to test if a chart should have drag/drop enabled, looking at
* its options.
*
* @private
* @function isChartDraggable
* @param {Highcharts.Chart} chart
* The chart to test.
* @return {boolean}
* True if the chart is drag/droppable.
*/
function isChartDraggable(chart) {
let i = chart.series ? chart.series.length : 0;
if ((chart.hasCartesianSeries && !chart.polar) ||
chart.mapView) {
while (i--) {
if (chart.series[i].options.dragDrop &&
isSeriesDraggable(chart.series[i])) {
return true;
}
}
}
return false;
}
/**
* Utility function to test if a point is movable (any of its props can be
* dragged by a move, not just individually).
*
* @private
* @function isPointMovable
* @param {Highcharts.Point} point
* The point to test.
* @return {boolean}
* True if the point is movable.
*/
function isPointMovable(point) {
const series = point.series, chart = series.chart, seriesDragDropOptions = series.options.dragDrop || {}, pointDragDropOptions = point.options && point.options.dragDrop, updateProps = series.dragDropProps;
let p, hasMovableX, hasMovableY;
// eslint-disable-next-line guard-for-in
for (const key in updateProps) {
p = updateProps[key];
if (p.axis === 'x' && p.move) {
hasMovableX = true;
}
else if (p.axis === 'y' && p.move) {
hasMovableY = true;
}
}
// We can only move the point if draggableX/Y is set, even if all the
// individual prop options are set.
return ((seriesDragDropOptions.draggableX && hasMovableX ||
seriesDragDropOptions.draggableY && hasMovableY) &&
!(pointDragDropOptions &&
pointDragDropOptions.draggableX === false &&
pointDragDropOptions.draggableY === false) &&
(!!(series.yAxis && series.xAxis) ||
chart.mapView));
}
/**
* Utility function to test if a series is using drag/drop, looking at its
* options.
*
* @private
* @function isSeriesDraggable
* @param {Highcharts.Series} series
* The series to test.
* @return {boolean}
* True if the series is using drag/drop.
*/
function isSeriesDraggable(series) {
const props = ['draggableX', 'draggableY'], dragDropProps = series.dragDropProps || {};
let val;
// Add optionNames from dragDropProps to the array of props to check for
for (const key of Object.keys(dragDropProps)) {
val = dragDropProps[key];
if (val.optionName) {
props.push(val.optionName);
}
}
// Loop over all options we have that could enable dragDrop for this
// series. If any of them are truthy, this series is draggable.
let i = props.length;
while (i--) {
if (series.options.dragDrop[props[i]]) {
return true;
}
}
}
/**
* On container mouse down. Init dragdrop if conditions are right.
*
* @private
* @function mouseDown
* @param {Highcharts.PointerEventObject} e
* The mouse down event.
* @param {Highcharts.Chart} chart
* The chart we are clicking.
*/
function mouseDown(e, chart) {
const dragPoint = chart.hoverPoint, dragDropOptions = merge(dragPoint && dragPoint.series.options.dragDrop, dragPoint && dragPoint.options.dragDrop), draggableX = dragDropOptions.draggableX || false, draggableY = dragDropOptions.draggableY || false;
// Reset cancel click
chart.cancelClick = false;
// Ignore if:
if (
// Option is disabled for the point
!(draggableX || draggableY) ||
// Zoom/pan key is pressed
chart.zoomOrPanKeyPressed(e) ||
// Dragging an annotation
chart.hasDraggedAnnotation) {
return;
}
// If we somehow get a mousedown event while we are dragging, cancel
if (chart.dragDropData && chart.dragDropData.isDragging) {
mouseUp(e, chart);
return;
}
// If this point is movable, start dragging it
if (dragPoint && isPointMovable(dragPoint)) {
chart.mouseIsDown = false; // Prevent zooming
initDragDrop(e, dragPoint);
dragPoint.firePointEvent('dragStart', e);
}
}
/**
* On container mouse move. Handle drag sensitivity and fire drag event.
*
* @private
* @function mouseMove
* @param {Highcharts.PointerEventObject} e
* The mouse move event.
* @param {Highcharts.Chart} chart
* The chart we are moving across.
*/
function mouseMove(e, chart) {
// Ignore if zoom/pan key is pressed
if (chart.zoomOrPanKeyPressed(e)) {
return;
}
const dragDropData = chart.dragDropData;
let point, seriesDragDropOpts, newPoints, numNewPoints = 0, newPoint;
if (dragDropData && dragDropData.isDragging && dragDropData.point.series) {
point = dragDropData.point;
seriesDragDropOpts = point.series.options.dragDrop;
// No tooltip for dragging
e.preventDefault();
// Update sensitivity test if not passed yet
if (!dragDropData.draggedPastSensitivity) {
dragDropData.draggedPastSensitivity = hasDraggedPastSensitivity(e, chart, pick(point.options.dragDrop &&
point.options.dragDrop.dragSensitivity, seriesDragDropOpts &&
seriesDragDropOpts.dragSensitivity, DraggablePoints_DragDropDefaults.dragSensitivity));
}
// If we have dragged past dragSensitivity, run the mousemove handler
// for dragging
if (dragDropData.draggedPastSensitivity) {
// Find the new point values from the moving
dragDropData.newPoints = getNewPoints(dragDropData, e);
// If we are only dragging one point, add it to the event
newPoints = dragDropData.newPoints;
numNewPoints = DraggableChart_countProps(newPoints);
newPoint = numNewPoints === 1 ?
DraggableChart_getFirstProp(newPoints) :
null;
// Run the handler
point.firePointEvent('drag', {
origin: dragDropData.origin,
newPoints: dragDropData.newPoints,
newPoint: newPoint && newPoint.newValues,
newPointId: newPoint && newPoint.point.id,
numNewPoints: numNewPoints,
chartX: e.chartX,
chartY: e.chartY
}, function () {
dragMove(e, point);
});
}
}
}
/**
* On container mouse up. Fire drop event and reset state.
*
* @private
* @function mouseUp
* @param {Highcharts.PointerEventObject} e
* The mouse up event.
* @param {Highcharts.Chart} chart
* The chart we were dragging in.
*/
function mouseUp(e, chart) {
const dragDropData = chart.dragDropData;
if (dragDropData &&
dragDropData.isDragging &&
dragDropData.draggedPastSensitivity &&
dragDropData.point.series) {
const point = dragDropData.point, newPoints = dragDropData.newPoints, numNewPoints = DraggableChart_countProps(newPoints), newPoint = numNewPoints === 1 ?
DraggableChart_getFirstProp(newPoints) :
null;
// Hide the drag handles
if (chart.dragHandles) {
chart.hideDragHandles();
}
// Prevent default action
e.preventDefault();
chart.cancelClick = true;
// Fire the event, with a default handler that updates the points
point.firePointEvent('drop', {
origin: dragDropData.origin,
chartX: e.chartX,
chartY: e.chartY,
newPoints: newPoints,
numNewPoints: numNewPoints,
newPoint: newPoint && newPoint.newValues,
newPointId: newPoint && newPoint.point.id
}, function () {
updatePoints(chart);
});
}
// Reset
delete chart.dragDropData;
// Clean up the drag guide box if it exists. This is always added on
// drag start, even if user is overriding events.
if (chart.dragGuideBox) {
chart.dragGuideBox.destroy();
delete chart.dragGuideBox;
}
}
/**
* Add event listener to Chart.render that checks whether or not we should add
* dragdrop.
* @private
*/
function onChartRender() {
// If we don't have dragDrop events, see if we should add them
if (!this.hasAddedDragDropEvents) {
addDragDropEvents(this);
}
}
/**
* Resize the guide box according to point options and a difference in mouse
* positions. Handles reversed axes.
*
* @private
* @function resizeGuideBox
* @param {Highcharts.Point} point
* The point that is being resized.
* @param {number} dX
* Difference in X position.
* @param {number} dY
* Difference in Y position.
*/
function resizeGuideBox(point, dX, dY) {
const series = point.series, chart = series.chart, dragDropData = chart.dragDropData, resizeProp = series.dragDropProps[dragDropData.updateProp],
// `dragDropProp.resizeSide` holds info on which side to resize.
newPoint = dragDropData.newPoints[point.id].newValues, resizeSide = typeof resizeProp.resizeSide === 'function' ?
resizeProp.resizeSide(newPoint, point) : resizeProp.resizeSide;
// Call resize hook if it is defined
if (resizeProp.beforeResize) {
resizeProp.beforeResize(chart.dragGuideBox, newPoint, point);
}
// Do the resize
resizeRect(chart.dragGuideBox, resizeProp.axis === 'x' && series.xAxis.reversed ||
resizeProp.axis === 'y' && series.yAxis.reversed ?
flipResizeSide(resizeSide) : resizeSide, {
x: resizeProp.axis === 'x' ?
dX - (dragDropData.origin.prevdX || 0) : 0,
y: resizeProp.axis === 'y' ?
dY - (dragDropData.origin.prevdY || 0) : 0
});
}
/**
* Resize a rect element on one side. The element is modified.
*
* @private
* @function resizeRect
* @param {Highcharts.SVGElement} rect
* Rect element to resize.
* @param {string} updateSide
* Which side of the rect to update. Can be `left`, `right`, `top` or
* `bottom`.
* @param {Highcharts.PositionObject} update
* Object with x and y properties, detailing how much to resize each
* dimension.
* @return {void}
*/
function resizeRect(rect, updateSide, update) {
let resizeAttrs;
switch (updateSide) {
case 'left':
resizeAttrs = {
x: rect.attr('x') + update.x,
width: Math.max(1, rect.attr('width') - update.x)
};
break;
case 'right':
resizeAttrs = {
width: Math.max(1, rect.attr('width') + update.x)
};
break;
case 'top':
resizeAttrs = {
y: rect.attr('y') + update.y,
height: Math.max(1, rect.attr('height') - update.y)
};
break;
case 'bottom':
resizeAttrs = {
height: Math.max(1, rect.attr('height') + update.y)
};
break;
default:
}
rect.attr(resizeAttrs);
}
/**
* Update the points in a chart from dragDropData.newPoints.
*
* @private
* @function updatePoints
* @param {Highcharts.Chart} chart
* A chart with dragDropData.newPoints.
* @param {boolean} [animation=true]
* Animate updating points?
*/
function updatePoints(chart, animation) {
const newPoints = chart.dragDropData.newPoints, animOptions = animObject(animation);
chart.isDragDropAnimating = true;
let newPoint;
// Update the points
for (const key of Object.keys(newPoints)) {
newPoint = newPoints[key];
newPoint.point.update(newPoint.newValues, false);
}
chart.redraw(animOptions);
// Clear the isAnimating flag after animation duration is complete.
// The complete handler for animation seems to have bugs at this time, so
// we have to use a timeout instead.
setTimeout(() => {
delete chart.isDragDropAnimating;
if (chart.hoverPoint && !chart.dragHandles) {
chart.hoverPoint.showDragHandles();
}
}, animOptions.duration);
}
/* *
*
* Default Export
*
* */
const DraggableChart = {
compose,
flipResizeSide,
initDragDrop
};
/* harmony default export */ const DraggablePoints_DraggableChart = (DraggableChart);
;// ./code/es-modules/Extensions/DraggablePoints/DragDropProps.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* Authors: Øystein Moseng, Torstein Hønsi, Jon A. Nygård
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
const { flipResizeSide: DragDropProps_flipResizeSide } = DraggablePoints_DraggableChart;
const { isNumber, merge: DragDropProps_merge, pick: DragDropProps_pick } = (external_highcharts_src_js_default_default());
/* *
*
* Constants
*
* */
// Line series - only draggableX/Y, no drag handles
const line = {
x: {
axis: 'x',
move: true
},
y: {
axis: 'y',
move: true
}
};
// Flag series - same as line/scatter
const flags = line;
// Column series - x can be moved, y can only be resized. Note extra
// functionality for handling upside down columns (below threshold).
const column = {
x: {
axis: 'x',
move: true
},
y: {
axis: 'y',
move: false,
resize: true,
// Force guideBox start coordinates
beforeResize: (guideBox, pointVals, point) => {
// We need to ensure that guideBox always starts at threshold.
// We flip whether or not we update the top or bottom of the guide
// box at threshold, but if we drag the mouse fast, the top has not
// reached threshold before we cross over and update the bottom.
const plotThreshold = DragDropProps_pick(point.yBottom, // Added support for stacked series. (#18741)
point.series.translatedThreshold), plotY = guideBox.attr('y'), threshold = isNumber(point.stackY) ? (point.stackY - (point.y || 0)) : point.series.options.threshold || 0, y = threshold + pointVals.y;
let height, diff;
if (point.series.yAxis.reversed ? y < threshold : y >= threshold) {
// Above threshold - always set height to hit the threshold
height = guideBox.attr('height');
diff = plotThreshold ? plotThreshold - plotY - height : 0;
guideBox.attr({
height: Math.max(0, Math.round(height + diff))
});
}
else {
// Below - always set y to start at threshold
guideBox.attr({
y: Math.round(plotY + (plotThreshold ? plotThreshold - plotY : 0))
});
}
},
// Flip the side of the resize handle if column is below threshold.
// Make sure we remove the handle on the other side.
resizeSide: (pointVals, point) => {
const chart = point.series.chart, dragHandles = chart.dragHandles, side = pointVals.y >= (point.series.options.threshold || 0) ?
'top' : 'bottom', flipSide = DragDropProps_flipResizeSide(side);
// Force remove handle on other side
if (dragHandles && dragHandles[flipSide]) {
dragHandles[flipSide].destroy();
delete dragHandles[flipSide];
}
return side;
},
// Position handle at bottom if column is below threshold
handlePositioner: (point) => {
const bBox = (point.shapeArgs ||
(point.graphic && point.graphic.getBBox()) ||
{}), reversed = point.series.yAxis.reversed, threshold = point.series.options.threshold || 0, y = point.y || 0, bottom = (!reversed && y >= threshold) ||
(reversed && y < threshold);
return {
x: bBox.x || 0,
y: bottom ? (bBox.y || 0) : (bBox.y || 0) + (bBox.height || 0)
};
},
// Horizontal handle
handleFormatter: (point) => {
const shapeArgs = point.shapeArgs || {}, radius = shapeArgs.r || 0, // Rounding of bar corners
width = shapeArgs.width || 0, centerX = width / 2;
return [
// Left wick
['M', radius, 0],
['L', centerX - 5, 0],
// Circle
['A', 1, 1, 0, 0, 0, centerX + 5, 0],
['A', 1, 1, 0, 0, 0, centerX - 5, 0],
// Right wick
['M', centerX + 5, 0],
['L', width - radius, 0]
];
}
}
};
// Boxplot series - move x, resize or move low/q1/q3/high
const boxplot = {
x: column.x,
/**
* Allow low value to be dragged individually.
*
* @type {boolean}
* @default true
* @requires modules/draggable-points
* @apioption plotOptions.boxplot.dragDrop.draggableLow
*/
low: {
optionName: 'draggableLow',
axis: 'y',
move: true,
resize: true,
resizeSide: 'bottom',
handlePositioner: (point) => ({
x: point.shapeArgs.x || 0,
y: point.lowPlot
}),
handleFormatter: column.y.handleFormatter,
propValidate: (val, point) => (val <= point.q1)
},
/**
* Allow Q1 value to be dragged individually.
*
* @type {boolean}
* @default true
* @requires modules/draggable-points
* @apioption plotOptions.boxplot.dragDrop.draggableQ1
*/
q1: {
optionName: 'draggableQ1',
axis: 'y',
move: true,
resize: true,
resizeSide: 'bottom',
handlePositioner: (point) => ({
x: point.shapeArgs.x || 0,
y: point.q1Plot
}),
handleFormatter: column.y.handleFormatter,
propValidate: (val, point) => (val <= point.median && val >= point.low)
},
median: {
// Median cannot be dragged individually, just move the whole
// point for this.
axis: 'y',
move: true
},
/**
* Allow Q3 value to be dragged individually.
*
*