|
|
@@ -0,0 +1,1397 @@
|
|
|
+/* *
|
|
|
+ *
|
|
|
+ * (c) 2009-2017 Highsoft, Black Label
|
|
|
+ *
|
|
|
+ * License: www.highcharts.com/license
|
|
|
+ *
|
|
|
+ * */
|
|
|
+'use strict';
|
|
|
+import H from '../parts/Globals.js';
|
|
|
+
|
|
|
+import U from '../parts/Utilities.js';
|
|
|
+var attr = U.attr,
|
|
|
+ extend = U.extend,
|
|
|
+ isArray = U.isArray,
|
|
|
+ isNumber = U.isNumber,
|
|
|
+ isObject = U.isObject,
|
|
|
+ objectEach = U.objectEach,
|
|
|
+ pick = U.pick;
|
|
|
+
|
|
|
+import chartNavigationMixin from '../mixins/navigation.js';
|
|
|
+
|
|
|
+var doc = H.doc,
|
|
|
+ win = H.win,
|
|
|
+ addEvent = H.addEvent,
|
|
|
+ merge = H.merge,
|
|
|
+ fireEvent = H.fireEvent,
|
|
|
+ PREFIX = 'highcharts-';
|
|
|
+
|
|
|
+// IE 9-11 polyfill for Element.closest():
|
|
|
+function closestPolyfill(el, s) {
|
|
|
+ var ElementProto = win.Element.prototype,
|
|
|
+ elementMatches =
|
|
|
+ ElementProto.matches ||
|
|
|
+ ElementProto.msMatchesSelector ||
|
|
|
+ ElementProto.webkitMatchesSelector,
|
|
|
+ ret = null;
|
|
|
+
|
|
|
+ if (ElementProto.closest) {
|
|
|
+ ret = ElementProto.closest.call(el, s);
|
|
|
+ } else {
|
|
|
+ do {
|
|
|
+ if (elementMatches.call(el, s)) {
|
|
|
+ return el;
|
|
|
+ }
|
|
|
+ el = el.parentElement || el.parentNode;
|
|
|
+
|
|
|
+ } while (el !== null && el.nodeType === 1);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * @private
|
|
|
+ * @interface bindingsUtils
|
|
|
+ */
|
|
|
+var bindingsUtils = {
|
|
|
+ /**
|
|
|
+ * Update size of background (rect) in some annotations: Measure, Simple
|
|
|
+ * Rect.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function bindingsUtils.updateRectSize
|
|
|
+ *
|
|
|
+ * @param {global.Event} event
|
|
|
+ * Normalized browser event
|
|
|
+ *
|
|
|
+ * @param {Highcharts.Annotation} annotation
|
|
|
+ * Annotation to be updated
|
|
|
+ */
|
|
|
+ updateRectSize: function (event, annotation) {
|
|
|
+ var chart = annotation.chart,
|
|
|
+ options = annotation.options.typeOptions,
|
|
|
+ coords = chart.pointer.getCoordinates(event),
|
|
|
+ width = coords.xAxis[0].value - options.point.x,
|
|
|
+ height = options.point.y - coords.yAxis[0].value;
|
|
|
+
|
|
|
+ annotation.update({
|
|
|
+ typeOptions: {
|
|
|
+ background: {
|
|
|
+ width: chart.inverted ? height : width,
|
|
|
+ height: chart.inverted ? width : height
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get field type according to value
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function bindingsUtils.getFieldType
|
|
|
+ *
|
|
|
+ * @param {*} value
|
|
|
+ * Atomic type (one of: string, number, boolean)
|
|
|
+ *
|
|
|
+ * @return {string}
|
|
|
+ * Field type (one of: text, number, checkbox)
|
|
|
+ */
|
|
|
+ getFieldType: function (value) {
|
|
|
+ return {
|
|
|
+ 'string': 'text',
|
|
|
+ 'number': 'number',
|
|
|
+ 'boolean': 'checkbox'
|
|
|
+ }[typeof value];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+H.NavigationBindings = function (chart, options) {
|
|
|
+ this.chart = chart;
|
|
|
+ this.options = options;
|
|
|
+ this.eventsToUnbind = [];
|
|
|
+ this.container = doc.getElementsByClassName(
|
|
|
+ this.options.bindingsClassName
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// Define which options from annotations should show up in edit box:
|
|
|
+H.NavigationBindings.annotationsEditable = {
|
|
|
+ // `typeOptions` are always available
|
|
|
+ // Nested and shared options:
|
|
|
+ nestedOptions: {
|
|
|
+ labelOptions: ['style', 'format', 'backgroundColor'],
|
|
|
+ labels: ['style'],
|
|
|
+ label: ['style'],
|
|
|
+ style: ['fontSize', 'color'],
|
|
|
+ background: ['fill', 'strokeWidth', 'stroke'],
|
|
|
+ innerBackground: ['fill', 'strokeWidth', 'stroke'],
|
|
|
+ outerBackground: ['fill', 'strokeWidth', 'stroke'],
|
|
|
+ shapeOptions: ['fill', 'strokeWidth', 'stroke'],
|
|
|
+ shapes: ['fill', 'strokeWidth', 'stroke'],
|
|
|
+ line: ['strokeWidth', 'stroke'],
|
|
|
+ backgroundColors: [true],
|
|
|
+ connector: ['fill', 'strokeWidth', 'stroke'],
|
|
|
+ crosshairX: ['strokeWidth', 'stroke'],
|
|
|
+ crosshairY: ['strokeWidth', 'stroke']
|
|
|
+ },
|
|
|
+ // Simple shapes:
|
|
|
+ circle: ['shapes'],
|
|
|
+ verticalLine: [],
|
|
|
+ label: ['labelOptions'],
|
|
|
+ // Measure
|
|
|
+ measure: ['background', 'crosshairY', 'crosshairX'],
|
|
|
+ // Others:
|
|
|
+ fibonacci: [],
|
|
|
+ tunnel: ['background', 'line', 'height'],
|
|
|
+ pitchfork: ['innerBackground', 'outerBackground'],
|
|
|
+ rect: ['shapes'],
|
|
|
+ // Crooked lines, elliots, arrows etc:
|
|
|
+ crookedLine: []
|
|
|
+};
|
|
|
+
|
|
|
+// Define non editable fields per annotation, for example Rectangle inherits
|
|
|
+// options from Measure, but crosshairs are not available
|
|
|
+H.NavigationBindings.annotationsNonEditable = {
|
|
|
+ rectangle: ['crosshairX', 'crosshairY', 'label']
|
|
|
+};
|
|
|
+
|
|
|
+extend(H.NavigationBindings.prototype, {
|
|
|
+ // Private properties added by bindings:
|
|
|
+
|
|
|
+ // Active (selected) annotation that is editted through popup/forms
|
|
|
+ // activeAnnotation: Annotation
|
|
|
+
|
|
|
+ // Holder for current step, used on mouse move to update bound object
|
|
|
+ // mouseMoveEvent: function () {}
|
|
|
+
|
|
|
+ // Next event in `step` array to be called on chart's click
|
|
|
+ // nextEvent: function () {}
|
|
|
+
|
|
|
+ // Index in the `step` array of the current event
|
|
|
+ // stepIndex: 0
|
|
|
+
|
|
|
+ // Flag to determine if current binding has steps
|
|
|
+ // steps: true|false
|
|
|
+
|
|
|
+ // Bindings holder for all events
|
|
|
+ // selectedButton: {}
|
|
|
+
|
|
|
+ // Holder for user options, returned from `start` event, and passed on to
|
|
|
+ // `step`'s' and `end`.
|
|
|
+ // currentUserDetails: {}
|
|
|
+ /**
|
|
|
+ * Initi all events conencted to NavigationBindings.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#initEvents
|
|
|
+ */
|
|
|
+ initEvents: function () {
|
|
|
+ var navigation = this,
|
|
|
+ chart = navigation.chart,
|
|
|
+ bindingsContainer = navigation.container,
|
|
|
+ options = navigation.options;
|
|
|
+
|
|
|
+ // Shorthand object for getting events for buttons:
|
|
|
+ navigation.boundClassNames = {};
|
|
|
+
|
|
|
+ objectEach(options.bindings, function (value) {
|
|
|
+ navigation.boundClassNames[value.className] = value;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Handle multiple containers with the same class names:
|
|
|
+ [].forEach.call(bindingsContainer, function (subContainer) {
|
|
|
+ navigation.eventsToUnbind.push(
|
|
|
+ addEvent(
|
|
|
+ subContainer,
|
|
|
+ 'click',
|
|
|
+ function (event) {
|
|
|
+ var bindings = navigation.getButtonEvents(
|
|
|
+ bindingsContainer,
|
|
|
+ event
|
|
|
+ );
|
|
|
+
|
|
|
+ if (bindings) {
|
|
|
+ navigation.bindingsButtonClick(
|
|
|
+ bindings.button,
|
|
|
+ bindings.events,
|
|
|
+ event
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ objectEach(options.events || {}, function (callback, eventName) {
|
|
|
+ if (H.isFunction(callback)) {
|
|
|
+ navigation.eventsToUnbind.push(
|
|
|
+ addEvent(
|
|
|
+ navigation,
|
|
|
+ eventName,
|
|
|
+ callback
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ navigation.eventsToUnbind.push(
|
|
|
+ addEvent(chart.container, 'click', function (e) {
|
|
|
+ if (
|
|
|
+ !chart.cancelClick &&
|
|
|
+ chart.isInsidePlot(
|
|
|
+ e.chartX - chart.plotLeft,
|
|
|
+ e.chartY - chart.plotTop
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ navigation.bindingsChartClick(this, e);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ );
|
|
|
+ navigation.eventsToUnbind.push(
|
|
|
+ addEvent(chart.container, 'mousemove', function (e) {
|
|
|
+ navigation.bindingsContainerMouseMove(this, e);
|
|
|
+ })
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Common chart.update() delegation, shared between bindings and exporting.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#initUpdate
|
|
|
+ */
|
|
|
+ initUpdate: function () {
|
|
|
+ var navigation = this;
|
|
|
+
|
|
|
+ chartNavigationMixin.addUpdate(
|
|
|
+ function (options) {
|
|
|
+ navigation.update(options);
|
|
|
+ },
|
|
|
+ this.chart
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Hook for click on a button, method selcts/unselects buttons,
|
|
|
+ * then calls `bindings.init` callback.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#bindingsButtonClick
|
|
|
+ *
|
|
|
+ * @param {Highcharts.HTMLDOMElement} [button]
|
|
|
+ * Clicked button
|
|
|
+ *
|
|
|
+ * @param {object} [events]
|
|
|
+ * Events passed down from bindings (`init`, `start`, `step`, `end`)
|
|
|
+ *
|
|
|
+ * @param {global.Event} [clickEvent]
|
|
|
+ * Browser's click event
|
|
|
+ */
|
|
|
+ bindingsButtonClick: function (button, events, clickEvent) {
|
|
|
+ var navigation = this,
|
|
|
+ chart = navigation.chart;
|
|
|
+
|
|
|
+ if (navigation.selectedButtonElement) {
|
|
|
+ fireEvent(
|
|
|
+ navigation,
|
|
|
+ 'deselectButton',
|
|
|
+ { button: navigation.selectedButtonElement }
|
|
|
+ );
|
|
|
+
|
|
|
+ if (navigation.nextEvent) {
|
|
|
+ // Remove in-progress annotations adders:
|
|
|
+ if (
|
|
|
+ navigation.currentUserDetails &&
|
|
|
+ navigation.currentUserDetails.coll === 'annotations'
|
|
|
+ ) {
|
|
|
+ chart.removeAnnotation(navigation.currentUserDetails);
|
|
|
+ }
|
|
|
+ navigation.mouseMoveEvent = navigation.nextEvent = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ navigation.selectedButton = events;
|
|
|
+ navigation.selectedButtonElement = button;
|
|
|
+
|
|
|
+ fireEvent(navigation, 'selectButton', { button: button });
|
|
|
+
|
|
|
+ // Call "init" event, for example to open modal window
|
|
|
+ if (events.init) {
|
|
|
+ events.init.call(navigation, button, clickEvent);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (events.start || events.steps) {
|
|
|
+ chart.renderer.boxWrapper.addClass(PREFIX + 'draw-mode');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Hook for click on a chart, first click on a chart calls `start` event,
|
|
|
+ * then on all subsequent clicks iterate over `steps` array.
|
|
|
+ * When finished, calls `end` event.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#bindingsChartClick
|
|
|
+ *
|
|
|
+ * @param {Highcharts.Chart} chart
|
|
|
+ * Chart that click was performed on.
|
|
|
+ *
|
|
|
+ * @param {global.Event} clickEvent
|
|
|
+ * Browser's click event.
|
|
|
+ */
|
|
|
+ bindingsChartClick: function (chartContainer, clickEvent) {
|
|
|
+ var navigation = this,
|
|
|
+ chart = navigation.chart,
|
|
|
+ selectedButton = navigation.selectedButton,
|
|
|
+ svgContainer = chart.renderer.boxWrapper;
|
|
|
+
|
|
|
+ // Click outside popups, should close them and deselect the annotation
|
|
|
+ if (
|
|
|
+ navigation.activeAnnotation &&
|
|
|
+ !clickEvent.activeAnnotation &&
|
|
|
+ // Element could be removed in the child action, e.g. button
|
|
|
+ clickEvent.target.parentNode &&
|
|
|
+ // TO DO: Polyfill for IE11?
|
|
|
+ !closestPolyfill(clickEvent.target, '.' + PREFIX + 'popup')
|
|
|
+ ) {
|
|
|
+ fireEvent(navigation, 'closePopup');
|
|
|
+ navigation.deselectAnnotation();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!selectedButton || !selectedButton.start) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!navigation.nextEvent) {
|
|
|
+ // Call init method:
|
|
|
+ navigation.currentUserDetails = selectedButton.start.call(
|
|
|
+ navigation,
|
|
|
+ clickEvent
|
|
|
+ );
|
|
|
+
|
|
|
+ // If steps exists (e.g. Annotations), bind them:
|
|
|
+ if (selectedButton.steps) {
|
|
|
+ navigation.stepIndex = 0;
|
|
|
+ navigation.steps = true;
|
|
|
+ navigation.mouseMoveEvent = navigation.nextEvent =
|
|
|
+ selectedButton.steps[navigation.stepIndex];
|
|
|
+ } else {
|
|
|
+
|
|
|
+ fireEvent(
|
|
|
+ navigation,
|
|
|
+ 'deselectButton',
|
|
|
+ { button: navigation.selectedButtonElement }
|
|
|
+ );
|
|
|
+ svgContainer.removeClass(PREFIX + 'draw-mode');
|
|
|
+ navigation.steps = false;
|
|
|
+ navigation.selectedButton = null;
|
|
|
+ // First click is also the last one:
|
|
|
+ if (selectedButton.end) {
|
|
|
+ selectedButton.end.call(
|
|
|
+ navigation,
|
|
|
+ clickEvent,
|
|
|
+ navigation.currentUserDetails
|
|
|
+ );
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+
|
|
|
+ navigation.nextEvent(
|
|
|
+ clickEvent,
|
|
|
+ navigation.currentUserDetails
|
|
|
+ );
|
|
|
+
|
|
|
+ if (navigation.steps) {
|
|
|
+
|
|
|
+ navigation.stepIndex++;
|
|
|
+
|
|
|
+ if (selectedButton.steps[navigation.stepIndex]) {
|
|
|
+ // If we have more steps, bind them one by one:
|
|
|
+ navigation.mouseMoveEvent = navigation.nextEvent =
|
|
|
+ selectedButton.steps[navigation.stepIndex];
|
|
|
+ } else {
|
|
|
+ fireEvent(
|
|
|
+ navigation,
|
|
|
+ 'deselectButton',
|
|
|
+ { button: navigation.selectedButtonElement }
|
|
|
+ );
|
|
|
+ svgContainer.removeClass(PREFIX + 'draw-mode');
|
|
|
+ // That was the last step, call end():
|
|
|
+ if (selectedButton.end) {
|
|
|
+ selectedButton.end.call(
|
|
|
+ navigation,
|
|
|
+ clickEvent,
|
|
|
+ navigation.currentUserDetails
|
|
|
+ );
|
|
|
+ }
|
|
|
+ navigation.nextEvent = false;
|
|
|
+ navigation.mouseMoveEvent = false;
|
|
|
+ navigation.selectedButton = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Hook for mouse move on a chart's container. It calls current step.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#bindingsContainerMouseMove
|
|
|
+ *
|
|
|
+ * @param {Highcharts.HTMLDOMElement} container
|
|
|
+ * Chart's container.
|
|
|
+ *
|
|
|
+ * @param {global.Event} moveEvent
|
|
|
+ * Browser's move event.
|
|
|
+ */
|
|
|
+ bindingsContainerMouseMove: function (container, moveEvent) {
|
|
|
+ if (this.mouseMoveEvent) {
|
|
|
+ this.mouseMoveEvent(
|
|
|
+ moveEvent,
|
|
|
+ this.currentUserDetails
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Translate fields (e.g. `params.period` or `marker.styles.color`) to
|
|
|
+ * Highcharts options object (e.g. `{ params: { period } }`).
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#fieldsToOptions
|
|
|
+ *
|
|
|
+ * @param {object} fields
|
|
|
+ * Fields from popup form.
|
|
|
+ *
|
|
|
+ * @param {object} config
|
|
|
+ * Default config to be modified.
|
|
|
+ *
|
|
|
+ * @return {object}
|
|
|
+ * Modified config
|
|
|
+ */
|
|
|
+ fieldsToOptions: function (fields, config) {
|
|
|
+ objectEach(fields, function (value, field) {
|
|
|
+ var parsedValue = parseFloat(value),
|
|
|
+ path = field.split('.'),
|
|
|
+ parent = config,
|
|
|
+ pathLength = path.length - 1;
|
|
|
+
|
|
|
+ // If it's a number (not "forma" options), parse it:
|
|
|
+ if (
|
|
|
+ isNumber(parsedValue) &&
|
|
|
+ !value.match(/px/g) &&
|
|
|
+ !field.match(/format/g)
|
|
|
+ ) {
|
|
|
+ value = parsedValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove empty strings or values like 0
|
|
|
+ if (value !== '' && value !== 'undefined') {
|
|
|
+ path.forEach(function (name, index) {
|
|
|
+ var nextName = pick(path[index + 1], '');
|
|
|
+
|
|
|
+ if (pathLength === index) {
|
|
|
+ // Last index, put value:
|
|
|
+ parent[name] = value;
|
|
|
+ } else if (!parent[name]) {
|
|
|
+ // Create middle property:
|
|
|
+ parent[name] = nextName.match(/\d/g) ? [] : {};
|
|
|
+ parent = parent[name];
|
|
|
+ } else {
|
|
|
+ // Jump into next property
|
|
|
+ parent = parent[name];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return config;
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Shorthand method to deselect an annotation.
|
|
|
+ *
|
|
|
+ * @function Highcharts.NavigationBindings#deselectAnnotation
|
|
|
+ */
|
|
|
+ deselectAnnotation: function () {
|
|
|
+ if (this.activeAnnotation) {
|
|
|
+ this.activeAnnotation.setControlPointsVisibility(false);
|
|
|
+ this.activeAnnotation = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Generates API config for popup in the same format as options for
|
|
|
+ * Annotation object.
|
|
|
+ *
|
|
|
+ * @function Highcharts.NavigationBindings#annotationToFields
|
|
|
+ *
|
|
|
+ * @param {Highcharts.Annotation} annotation
|
|
|
+ * Annotations object
|
|
|
+ *
|
|
|
+ * @return {object}
|
|
|
+ * Annotation options to be displayed in popup box
|
|
|
+ */
|
|
|
+ annotationToFields: function (annotation) {
|
|
|
+ var options = annotation.options,
|
|
|
+ editables = H.NavigationBindings.annotationsEditable,
|
|
|
+ nestedEditables = editables.nestedOptions,
|
|
|
+ getFieldType = this.utils.getFieldType,
|
|
|
+ type = pick(
|
|
|
+ options.type,
|
|
|
+ options.shapes && options.shapes[0] &&
|
|
|
+ options.shapes[0].type,
|
|
|
+ options.labels && options.labels[0] &&
|
|
|
+ options.labels[0].itemType,
|
|
|
+ 'label'
|
|
|
+ ),
|
|
|
+ nonEditables = H.NavigationBindings
|
|
|
+ .annotationsNonEditable[options.langKey] || [],
|
|
|
+ visualOptions = {
|
|
|
+ langKey: options.langKey,
|
|
|
+ type: type
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Nested options traversing. Method goes down to the options and copies
|
|
|
+ * allowed options (with values) to new object, which is last parameter:
|
|
|
+ * "parent".
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#annotationToFields.traverse
|
|
|
+ *
|
|
|
+ * @param {*} option
|
|
|
+ * Atomic type or object/array
|
|
|
+ *
|
|
|
+ * @param {string} key
|
|
|
+ * Option name, for example "visible" or "x", "y"
|
|
|
+ *
|
|
|
+ * @param {object} allowed
|
|
|
+ * Editables from H.NavigationBindings.annotationsEditable
|
|
|
+ *
|
|
|
+ * @param {object} parent
|
|
|
+ * Where new options will be assigned
|
|
|
+ */
|
|
|
+ function traverse(option, key, parentEditables, parent) {
|
|
|
+ var nextParent;
|
|
|
+
|
|
|
+ if (
|
|
|
+ parentEditables &&
|
|
|
+ nonEditables.indexOf(key) === -1 &&
|
|
|
+ (
|
|
|
+ (
|
|
|
+ parentEditables.indexOf &&
|
|
|
+ parentEditables.indexOf(key)
|
|
|
+ ) >= 0 ||
|
|
|
+ parentEditables[key] || // nested array
|
|
|
+ parentEditables === true // simple array
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ // Roots:
|
|
|
+ if (isArray(option)) {
|
|
|
+ parent[key] = [];
|
|
|
+
|
|
|
+ option.forEach(function (arrayOption, i) {
|
|
|
+ if (!isObject(arrayOption)) {
|
|
|
+ // Simple arrays, e.g. [String, Number, Boolean]
|
|
|
+ traverse(
|
|
|
+ arrayOption,
|
|
|
+ 0,
|
|
|
+ nestedEditables[key],
|
|
|
+ parent[key]
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ // Advanced arrays, e.g. [Object, Object]
|
|
|
+ parent[key][i] = {};
|
|
|
+ objectEach(
|
|
|
+ arrayOption,
|
|
|
+ function (nestedOption, nestedKey) {
|
|
|
+ traverse(
|
|
|
+ nestedOption,
|
|
|
+ nestedKey,
|
|
|
+ nestedEditables[key],
|
|
|
+ parent[key][i]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else if (isObject(option)) {
|
|
|
+ nextParent = {};
|
|
|
+ if (isArray(parent)) {
|
|
|
+ parent.push(nextParent);
|
|
|
+ nextParent[key] = {};
|
|
|
+ nextParent = nextParent[key];
|
|
|
+ } else {
|
|
|
+ parent[key] = nextParent;
|
|
|
+ }
|
|
|
+ objectEach(option, function (nestedOption, nestedKey) {
|
|
|
+ traverse(
|
|
|
+ nestedOption,
|
|
|
+ nestedKey,
|
|
|
+ key === 0 ? parentEditables : nestedEditables[key],
|
|
|
+ nextParent
|
|
|
+ );
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // Leaf:
|
|
|
+ if (key === 'format') {
|
|
|
+ parent[key] = [
|
|
|
+ H.format(
|
|
|
+ option,
|
|
|
+ annotation.labels[0].points[0]
|
|
|
+ ).toString(),
|
|
|
+ 'text'
|
|
|
+ ];
|
|
|
+ } else if (isArray(parent)) {
|
|
|
+ parent.push([option, getFieldType(option)]);
|
|
|
+ } else {
|
|
|
+ parent[key] = [option, getFieldType(option)];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ objectEach(options, function (option, key) {
|
|
|
+ if (key === 'typeOptions') {
|
|
|
+ visualOptions[key] = {};
|
|
|
+ objectEach(options[key], function (typeOption, typeKey) {
|
|
|
+ traverse(
|
|
|
+ typeOption,
|
|
|
+ typeKey,
|
|
|
+ nestedEditables,
|
|
|
+ visualOptions[key],
|
|
|
+ true
|
|
|
+ );
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ traverse(option, key, editables[type], visualOptions);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return visualOptions;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all class names for all parents in the element. Iterates until finds
|
|
|
+ * main container.
|
|
|
+ *
|
|
|
+ * @function Highcharts.NavigationBindings#getClickedClassNames
|
|
|
+ *
|
|
|
+ * @param {Highcharts.HTMLDOMElement}
|
|
|
+ * Container that event is bound to.
|
|
|
+ *
|
|
|
+ * @param {global.Event} event
|
|
|
+ * Browser's event.
|
|
|
+ *
|
|
|
+ * @return {Array<string>}
|
|
|
+ * Array of class names with corresponding elements
|
|
|
+ */
|
|
|
+ getClickedClassNames: function (container, event) {
|
|
|
+ var element = event.target,
|
|
|
+ classNames = [],
|
|
|
+ elemClassName;
|
|
|
+
|
|
|
+ while (element) {
|
|
|
+ elemClassName = attr(element, 'class');
|
|
|
+ if (elemClassName) {
|
|
|
+ classNames = classNames.concat(
|
|
|
+ elemClassName.split(' ').map(
|
|
|
+ function (name) { // eslint-disable-line no-loop-func
|
|
|
+ return [
|
|
|
+ name,
|
|
|
+ element
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ element = element.parentNode;
|
|
|
+
|
|
|
+ if (element === container) {
|
|
|
+ return classNames;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return classNames;
|
|
|
+
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Get events bound to a button. It's a custom event delegation to find all
|
|
|
+ * events connected to the element.
|
|
|
+ *
|
|
|
+ * @function Highcharts.NavigationBindings#getButtonEvents
|
|
|
+ *
|
|
|
+ * @param {Highcharts.HTMLDOMElement}
|
|
|
+ * Container that event is bound to.
|
|
|
+ *
|
|
|
+ * @param {global.Event} event
|
|
|
+ * Browser's event.
|
|
|
+ *
|
|
|
+ * @return {object}
|
|
|
+ * Oject with events (init, start, steps, and end)
|
|
|
+ */
|
|
|
+ getButtonEvents: function (container, event) {
|
|
|
+ var navigation = this,
|
|
|
+ classNames = this.getClickedClassNames(container, event),
|
|
|
+ bindings;
|
|
|
+
|
|
|
+
|
|
|
+ classNames.forEach(function (className) {
|
|
|
+ if (navigation.boundClassNames[className[0]] && !bindings) {
|
|
|
+ bindings = {
|
|
|
+ events: navigation.boundClassNames[className[0]],
|
|
|
+ button: className[1]
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return bindings;
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Bindings are just events, so the whole update process is simply
|
|
|
+ * removing old events and adding new ones.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#update
|
|
|
+ */
|
|
|
+ update: function (options) {
|
|
|
+ this.options = merge(true, this.options, options);
|
|
|
+ this.removeEvents();
|
|
|
+ this.initEvents();
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Remove all events created in the navigation.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @function Highcharts.NavigationBindings#removeEvents
|
|
|
+ */
|
|
|
+ removeEvents: function () {
|
|
|
+ this.eventsToUnbind.forEach(function (unbinder) {
|
|
|
+ unbinder();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ destroy: function () {
|
|
|
+ this.removeEvents();
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * General utils for bindings
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ * @name Highcharts.NavigationBindings#utils
|
|
|
+ * @type {bindingsUtils}
|
|
|
+ */
|
|
|
+ utils: bindingsUtils
|
|
|
+});
|
|
|
+
|
|
|
+H.Chart.prototype.initNavigationBindings = function () {
|
|
|
+ var chart = this,
|
|
|
+ options = chart.options;
|
|
|
+
|
|
|
+ if (options && options.navigation && options.navigation.bindings) {
|
|
|
+ chart.navigationBindings = new H.NavigationBindings(
|
|
|
+ chart,
|
|
|
+ options.navigation
|
|
|
+ );
|
|
|
+ chart.navigationBindings.initEvents();
|
|
|
+ chart.navigationBindings.initUpdate();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+addEvent(H.Chart, 'load', function () {
|
|
|
+ this.initNavigationBindings();
|
|
|
+});
|
|
|
+
|
|
|
+addEvent(H.Chart, 'destroy', function () {
|
|
|
+ if (this.navigationBindings) {
|
|
|
+ this.navigationBindings.destroy();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+addEvent(H.NavigationBindings, 'deselectButton', function () {
|
|
|
+ this.selectedButtonElement = null;
|
|
|
+});
|
|
|
+
|
|
|
+addEvent(H.Annotation, 'remove', function () {
|
|
|
+ if (this.chart.navigationBindings) {
|
|
|
+ this.chart.navigationBindings.deselectAnnotation();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+// Show edit-annotation form:
|
|
|
+function selectableAnnotation(annotationType) {
|
|
|
+ var originalClick = annotationType.prototype.defaultOptions.events &&
|
|
|
+ annotationType.prototype.defaultOptions.events.click;
|
|
|
+
|
|
|
+ function selectAndshowPopup(event) {
|
|
|
+ var annotation = this,
|
|
|
+ navigation = annotation.chart.navigationBindings,
|
|
|
+ prevAnnotation = navigation.activeAnnotation;
|
|
|
+
|
|
|
+ if (originalClick) {
|
|
|
+ originalClick.click.call(annotation, event);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (prevAnnotation !== annotation) {
|
|
|
+ // Select current:
|
|
|
+ navigation.deselectAnnotation();
|
|
|
+
|
|
|
+ navigation.activeAnnotation = annotation;
|
|
|
+ annotation.setControlPointsVisibility(true);
|
|
|
+
|
|
|
+ fireEvent(
|
|
|
+ navigation,
|
|
|
+ 'showPopup',
|
|
|
+ {
|
|
|
+ annotation: annotation,
|
|
|
+ formType: 'annotation-toolbar',
|
|
|
+ options: navigation.annotationToFields(annotation),
|
|
|
+ onSubmit: function (data) {
|
|
|
+
|
|
|
+ var config = {},
|
|
|
+ typeOptions;
|
|
|
+
|
|
|
+ if (data.actionType === 'remove') {
|
|
|
+ navigation.activeAnnotation = false;
|
|
|
+ navigation.chart.removeAnnotation(annotation);
|
|
|
+ } else {
|
|
|
+ navigation.fieldsToOptions(data.fields, config);
|
|
|
+ navigation.deselectAnnotation();
|
|
|
+
|
|
|
+ typeOptions = config.typeOptions;
|
|
|
+
|
|
|
+ if (annotation.options.type === 'measure') {
|
|
|
+ // Manually disable crooshars according to
|
|
|
+ // stroke width of the shape:
|
|
|
+ typeOptions.crosshairY.enabled =
|
|
|
+ typeOptions.crosshairY.strokeWidth !== 0;
|
|
|
+ typeOptions.crosshairX.enabled =
|
|
|
+ typeOptions.crosshairX.strokeWidth !== 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ annotation.update(config);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ // Deselect current:
|
|
|
+ navigation.deselectAnnotation();
|
|
|
+ fireEvent(navigation, 'closePopup');
|
|
|
+ }
|
|
|
+ // Let bubble event to chart.click:
|
|
|
+ event.activeAnnotation = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ H.merge(
|
|
|
+ true,
|
|
|
+ annotationType.prototype.defaultOptions.events,
|
|
|
+ {
|
|
|
+ click: selectAndshowPopup
|
|
|
+ }
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+if (H.Annotation) {
|
|
|
+ // Basic shapes:
|
|
|
+ selectableAnnotation(H.Annotation);
|
|
|
+
|
|
|
+ // Advanced annotations:
|
|
|
+ objectEach(H.Annotation.types, function (annotationType) {
|
|
|
+ selectableAnnotation(annotationType);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+H.setOptions({
|
|
|
+ /**
|
|
|
+ * @optionparent lang
|
|
|
+ */
|
|
|
+ lang: {
|
|
|
+ /**
|
|
|
+ * Configure the Popup strings in the chart. Requires the
|
|
|
+ * `annotations.js` or `annotations-advanced.src.js` module to be
|
|
|
+ * loaded.
|
|
|
+ *
|
|
|
+ * @since 7.0.0
|
|
|
+ * @type {Object}
|
|
|
+ * @product highcharts highstock
|
|
|
+ */
|
|
|
+ navigation: {
|
|
|
+ /**
|
|
|
+ * Translations for all field names used in popup.
|
|
|
+ *
|
|
|
+ * @product highcharts highstock
|
|
|
+ * @type {Object}
|
|
|
+ */
|
|
|
+ popup: {
|
|
|
+ simpleShapes: 'Simple shapes',
|
|
|
+ lines: 'Lines',
|
|
|
+ circle: 'Circle',
|
|
|
+ rectangle: 'Rectangle',
|
|
|
+ label: 'Label',
|
|
|
+ shapeOptions: 'Shape options',
|
|
|
+ typeOptions: 'Details',
|
|
|
+ fill: 'Fill',
|
|
|
+ format: 'Text',
|
|
|
+ strokeWidth: 'Line width',
|
|
|
+ stroke: 'Line color',
|
|
|
+ title: 'Title',
|
|
|
+ name: 'Name',
|
|
|
+ labelOptions: 'Label options',
|
|
|
+ labels: 'Labels',
|
|
|
+ backgroundColor: 'Background color',
|
|
|
+ backgroundColors: 'Background colors',
|
|
|
+ borderColor: 'Border color',
|
|
|
+ borderRadius: 'Border radius',
|
|
|
+ borderWidth: 'Border width',
|
|
|
+ style: 'Style',
|
|
|
+ padding: 'Padding',
|
|
|
+ fontSize: 'Font size',
|
|
|
+ color: 'Color',
|
|
|
+ height: 'Height',
|
|
|
+ shapes: 'Shape options'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * @optionparent navigation
|
|
|
+ * @product highcharts highstock
|
|
|
+ */
|
|
|
+ navigation: {
|
|
|
+ /**
|
|
|
+ * A CSS class name where all bindings will be attached to. Multiple
|
|
|
+ * charts on the same page should have separate class names to prevent
|
|
|
+ * duplicating events.
|
|
|
+ *
|
|
|
+ * Default value of versions < 7.0.4 `highcharts-bindings-wrapper`
|
|
|
+ *
|
|
|
+ * @since 7.0.0
|
|
|
+ * @type {string}
|
|
|
+ */
|
|
|
+ bindingsClassName: 'highcharts-bindings-container',
|
|
|
+ /**
|
|
|
+ * Bindings definitions for custom HTML buttons. Each binding implements
|
|
|
+ * simple event-driven interface:
|
|
|
+ *
|
|
|
+ * - `className`: classname used to bind event to
|
|
|
+ *
|
|
|
+ * - `init`: initial event, fired on button click
|
|
|
+ *
|
|
|
+ * - `start`: fired on first click on a chart
|
|
|
+ *
|
|
|
+ * - `steps`: array of sequential events fired one after another on each
|
|
|
+ * of users clicks
|
|
|
+ *
|
|
|
+ * - `end`: last event to be called after last step event
|
|
|
+ *
|
|
|
+ * @type {Highcharts.Dictionary<Highcharts.StockToolsBindingsObject>|*}
|
|
|
+ * @sample stock/stocktools/stocktools-thresholds
|
|
|
+ * Custom bindings in Highstock
|
|
|
+ * @since 7.0.0
|
|
|
+ * @product highcharts highstock
|
|
|
+ */
|
|
|
+ bindings: {
|
|
|
+ /**
|
|
|
+ * A circle annotation bindings. Includes `start` and one event in
|
|
|
+ * `steps` array.
|
|
|
+ *
|
|
|
+ * @type {Highcharts.StockToolsBindingsObject}
|
|
|
+ * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
|
|
|
+ */
|
|
|
+ circleAnnotation: {
|
|
|
+ /** @ignore */
|
|
|
+ className: 'highcharts-circle-annotation',
|
|
|
+ /** @ignore */
|
|
|
+ start: function (e) {
|
|
|
+ var coords = this.chart.pointer.getCoordinates(e),
|
|
|
+ navigation = this.chart.options.navigation,
|
|
|
+ controlPoints = [{
|
|
|
+ positioner: function (target) {
|
|
|
+ var xy = H.Annotation.MockPoint
|
|
|
+ .pointToPixels(
|
|
|
+ target.points[0]
|
|
|
+ ),
|
|
|
+ r = target.options.r;
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: xy.x + r * Math.cos(Math.PI / 4) -
|
|
|
+ this.graphic.width / 2,
|
|
|
+ y: xy.y + r * Math.sin(Math.PI / 4) -
|
|
|
+ this.graphic.height / 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+ events: {
|
|
|
+ // TRANSFORM RADIUS ACCORDING TO Y
|
|
|
+ // TRANSLATION
|
|
|
+ drag: function (e, target) {
|
|
|
+ var annotation = target.annotation,
|
|
|
+ position = this
|
|
|
+ .mouseMoveToTranslation(e);
|
|
|
+
|
|
|
+ target.setRadius(
|
|
|
+ Math.max(
|
|
|
+ target.options.r +
|
|
|
+ position.y /
|
|
|
+ Math.sin(Math.PI / 4),
|
|
|
+ 5
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ annotation.options.shapes[0] =
|
|
|
+ annotation.userOptions.shapes[0] =
|
|
|
+ target.options;
|
|
|
+
|
|
|
+ target.redraw(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }];
|
|
|
+
|
|
|
+ return this.chart.addAnnotation(
|
|
|
+ merge(
|
|
|
+ {
|
|
|
+ langKey: 'circle',
|
|
|
+ shapes: [{
|
|
|
+ type: 'circle',
|
|
|
+ point: {
|
|
|
+ xAxis: 0,
|
|
|
+ yAxis: 0,
|
|
|
+ x: coords.xAxis[0].value,
|
|
|
+ y: coords.yAxis[0].value
|
|
|
+ },
|
|
|
+ r: 5,
|
|
|
+ controlPoints: controlPoints
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ navigation
|
|
|
+ .annotationsOptions,
|
|
|
+ navigation
|
|
|
+ .bindings
|
|
|
+ .circleAnnotation
|
|
|
+ .annotationsOptions
|
|
|
+ )
|
|
|
+ );
|
|
|
+ },
|
|
|
+ /** @ignore */
|
|
|
+ steps: [
|
|
|
+ function (e, annotation) {
|
|
|
+ var point = annotation.options.shapes[0].point,
|
|
|
+ x = this.chart.xAxis[0].toPixels(point.x),
|
|
|
+ y = this.chart.yAxis[0].toPixels(point.y),
|
|
|
+ inverted = this.chart.inverted,
|
|
|
+ distance = Math.max(
|
|
|
+ Math.sqrt(
|
|
|
+ Math.pow(
|
|
|
+ inverted ? y - e.chartX : x - e.chartX,
|
|
|
+ 2
|
|
|
+ ) +
|
|
|
+ Math.pow(
|
|
|
+ inverted ? x - e.chartY : y - e.chartY,
|
|
|
+ 2
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ 5
|
|
|
+ );
|
|
|
+
|
|
|
+ annotation.update({
|
|
|
+ shapes: [{
|
|
|
+ r: distance
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * A rectangle annotation bindings. Includes `start` and one event
|
|
|
+ * in `steps` array.
|
|
|
+ *
|
|
|
+ * @type {Highcharts.StockToolsBindingsObject}
|
|
|
+ * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
|
|
|
+ */
|
|
|
+ rectangleAnnotation: {
|
|
|
+ /** @ignore */
|
|
|
+ className: 'highcharts-rectangle-annotation',
|
|
|
+ /** @ignore */
|
|
|
+ start: function (e) {
|
|
|
+ var coords = this.chart.pointer.getCoordinates(e),
|
|
|
+ navigation = this.chart.options.navigation,
|
|
|
+ x = coords.xAxis[0].value,
|
|
|
+ y = coords.yAxis[0].value,
|
|
|
+ controlPoints = [{
|
|
|
+ positioner: function (annotation) {
|
|
|
+ var xy = H.Annotation.MockPoint
|
|
|
+ .pointToPixels(
|
|
|
+ annotation.shapes[0].points[2]
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: xy.x - 4,
|
|
|
+ y: xy.y - 4
|
|
|
+ };
|
|
|
+ },
|
|
|
+ events: {
|
|
|
+ drag: function (e, target) {
|
|
|
+ var coords = this.chart.pointer
|
|
|
+ .getCoordinates(e),
|
|
|
+ x = coords.xAxis[0].value,
|
|
|
+ y = coords.yAxis[0].value,
|
|
|
+ shape = target.options.shapes[0],
|
|
|
+ points = shape.points;
|
|
|
+
|
|
|
+ // Top right point
|
|
|
+ points[1].x = x;
|
|
|
+ // Bottom right point (cursor position)
|
|
|
+ points[2].x = x;
|
|
|
+ points[2].y = y;
|
|
|
+ // Bottom left
|
|
|
+ points[3].y = y;
|
|
|
+
|
|
|
+ target.options.shapes[0].points = points;
|
|
|
+
|
|
|
+ target.redraw(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }];
|
|
|
+
|
|
|
+ return this.chart.addAnnotation(
|
|
|
+ merge(
|
|
|
+ {
|
|
|
+ langKey: 'rectangle',
|
|
|
+ shapes: [{
|
|
|
+ type: 'path',
|
|
|
+ points: [{
|
|
|
+ xAxis: 0,
|
|
|
+ yAxis: 0,
|
|
|
+ x: x,
|
|
|
+ y: y
|
|
|
+ }, {
|
|
|
+ xAxis: 0,
|
|
|
+ yAxis: 0,
|
|
|
+ x: x,
|
|
|
+ y: y
|
|
|
+ }, {
|
|
|
+ xAxis: 0,
|
|
|
+ yAxis: 0,
|
|
|
+ x: x,
|
|
|
+ y: y
|
|
|
+ }, {
|
|
|
+ xAxis: 0,
|
|
|
+ yAxis: 0,
|
|
|
+ x: x,
|
|
|
+ y: y
|
|
|
+ }]
|
|
|
+ }],
|
|
|
+ controlPoints: controlPoints
|
|
|
+ },
|
|
|
+ navigation
|
|
|
+ .annotationsOptions,
|
|
|
+ navigation
|
|
|
+ .bindings
|
|
|
+ .rectangleAnnotation
|
|
|
+ .annotationsOptions
|
|
|
+ )
|
|
|
+ );
|
|
|
+ },
|
|
|
+ /** @ignore */
|
|
|
+ steps: [
|
|
|
+ function (e, annotation) {
|
|
|
+ var points = annotation.options.shapes[0].points,
|
|
|
+ coords = this.chart.pointer.getCoordinates(e),
|
|
|
+ x = coords.xAxis[0].value,
|
|
|
+ y = coords.yAxis[0].value;
|
|
|
+
|
|
|
+ // Top right point
|
|
|
+ points[1].x = x;
|
|
|
+ // Bottom right point (cursor position)
|
|
|
+ points[2].x = x;
|
|
|
+ points[2].y = y;
|
|
|
+ // Bottom left
|
|
|
+ points[3].y = y;
|
|
|
+
|
|
|
+ annotation.update({
|
|
|
+ shapes: [{
|
|
|
+ points: points
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * A label annotation bindings. Includes `start` event only.
|
|
|
+ *
|
|
|
+ * @type {Highcharts.StockToolsBindingsObject}
|
|
|
+ * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}}
|
|
|
+ */
|
|
|
+ labelAnnotation: {
|
|
|
+ /** @ignore */
|
|
|
+ className: 'highcharts-label-annotation',
|
|
|
+ /** @ignore */
|
|
|
+ start: function (e) {
|
|
|
+ var coords = this.chart.pointer.getCoordinates(e),
|
|
|
+ navigation = this.chart.options.navigation,
|
|
|
+ controlPoints = [{
|
|
|
+ symbol: 'triangle-down',
|
|
|
+ positioner: function (target) {
|
|
|
+ if (!target.graphic.placed) {
|
|
|
+ return {
|
|
|
+ x: 0,
|
|
|
+ y: -9e7
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ var xy = H.Annotation.MockPoint
|
|
|
+ .pointToPixels(
|
|
|
+ target.points[0]
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: xy.x - this.graphic.width / 2,
|
|
|
+ y: xy.y - this.graphic.height / 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ // TRANSLATE POINT/ANCHOR
|
|
|
+ events: {
|
|
|
+ drag: function (e, target) {
|
|
|
+ var xy = this.mouseMoveToTranslation(e);
|
|
|
+
|
|
|
+ target.translatePoint(xy.x, xy.y);
|
|
|
+
|
|
|
+ target.annotation.labels[0].options =
|
|
|
+ target.options;
|
|
|
+
|
|
|
+ target.redraw(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ symbol: 'square',
|
|
|
+ positioner: function (target) {
|
|
|
+ if (!target.graphic.placed) {
|
|
|
+ return {
|
|
|
+ x: 0,
|
|
|
+ y: -9e7
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: target.graphic.alignAttr.x -
|
|
|
+ this.graphic.width / 2,
|
|
|
+ y: target.graphic.alignAttr.y -
|
|
|
+ this.graphic.height / 2
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ // TRANSLATE POSITION WITHOUT CHANGING THE
|
|
|
+ // ANCHOR
|
|
|
+ events: {
|
|
|
+ drag: function (e, target) {
|
|
|
+ var xy = this.mouseMoveToTranslation(e);
|
|
|
+
|
|
|
+ target.translate(xy.x, xy.y);
|
|
|
+
|
|
|
+ target.annotation.labels[0].options =
|
|
|
+ target.options;
|
|
|
+
|
|
|
+ target.redraw(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }];
|
|
|
+
|
|
|
+ return this.chart.addAnnotation(
|
|
|
+ merge(
|
|
|
+ {
|
|
|
+ langKey: 'label',
|
|
|
+ labelOptions: {
|
|
|
+ format: '{y:.2f}'
|
|
|
+ },
|
|
|
+ labels: [{
|
|
|
+ point: {
|
|
|
+ xAxis: 0,
|
|
|
+ yAxis: 0,
|
|
|
+ x: coords.xAxis[0].value,
|
|
|
+ y: coords.yAxis[0].value
|
|
|
+ },
|
|
|
+ overflow: 'none',
|
|
|
+ crop: true,
|
|
|
+ controlPoints: controlPoints
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ navigation
|
|
|
+ .annotationsOptions,
|
|
|
+ navigation
|
|
|
+ .bindings
|
|
|
+ .labelAnnotation
|
|
|
+ .annotationsOptions
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * Path where Highcharts will look for icons. Change this to use icons
|
|
|
+ * from a different server.
|
|
|
+ *
|
|
|
+ * @type {string}
|
|
|
+ * @default https://code.highcharts.com/8.0.0/gfx/stock-icons/
|
|
|
+ * @since 7.1.3
|
|
|
+ * @apioption navigation.iconsURL
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A `showPopup` event. Fired when selecting for example an annotation.
|
|
|
+ *
|
|
|
+ * @type {Function}
|
|
|
+ * @apioption navigation.events.showPopup
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A `closePopup` event. Fired when Popup should be hidden, for example
|
|
|
+ * when clicking on an annotation again.
|
|
|
+ *
|
|
|
+ * @type {Function}
|
|
|
+ * @apioption navigation.events.closePopup
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Event fired on a button click.
|
|
|
+ *
|
|
|
+ * @type {Function}
|
|
|
+ * @sample highcharts/annotations/gui/
|
|
|
+ * Change icon in a dropddown on event
|
|
|
+ * @sample highcharts/annotations/gui-buttons/
|
|
|
+ * Change button class on event
|
|
|
+ * @apioption navigation.events.selectButton
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Event fired when button state should change, for example after
|
|
|
+ * adding an annotation.
|
|
|
+ *
|
|
|
+ * @type {Function}
|
|
|
+ * @sample highcharts/annotations/gui/
|
|
|
+ * Change icon in a dropddown on event
|
|
|
+ * @sample highcharts/annotations/gui-buttons/
|
|
|
+ * Change button class on event
|
|
|
+ * @apioption navigation.events.deselectButton
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Events to communicate between Stock Tools and custom GUI.
|
|
|
+ *
|
|
|
+ * @since 7.0.0
|
|
|
+ * @product highcharts highstock
|
|
|
+ * @optionparent navigation.events
|
|
|
+ */
|
|
|
+ events: {},
|
|
|
+ /**
|
|
|
+ * Additional options to be merged into all annotations.
|
|
|
+ *
|
|
|
+ * @sample stock/stocktools/navigation-annotation-options
|
|
|
+ * Set red color of all line annotations
|
|
|
+ *
|
|
|
+ * @type {Highcharts.AnnotationsOptions}
|
|
|
+ * @extends annotations
|
|
|
+ * @exclude crookedLine, elliottWave, fibonacci, infinityLine,
|
|
|
+ * measure, pitchfork, tunnel, verticalLine
|
|
|
+ * @apioption navigation.annotationsOptions
|
|
|
+ */
|
|
|
+ annotationsOptions: {}
|
|
|
+ }
|
|
|
+});
|