UNPKG

highcharts

Version:
1,460 lines (1,444 loc) 87.9 kB
/** * @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. * *