Source: control2/hotspot/Circle.js

(function() {
    /**
     * @typedef {Help4.control2.hotspot.Hotspot.Params} Help4.control2.hotspot.Circle.Params
     * @property {Help4.control2.PositionXY} [point = {x: 0, y: 0}] - center point of hotspot
     * @property {Help4.control2.PositionLeftTop} [delta = {left: 0, top: 0}] - in case hotspot is moved
     * @property {string} [size = ''] - size of hotspot
     */

    /**
     * Creates a circle hotspot.
     * @augments Help4.control2.hotspot.Hotspot
     * @property {Help4.control2.PositionXY} point - center point of hotspot
     * @property {Help4.control2.PositionLeftTop} delta - in case hotspot is moved
     * @property {string} size - size of hotspot
     */
    Help4.control2.hotspot.Circle = class extends Help4.control2.hotspot.Hotspot {
        /**
         * @override
         * @param {Help4.control2.hotspot.Circle.Params} [params]
         */
        constructor(params) {
            const T = Help4.jscore.ControlBase.TYPES;
            super(params, {
                params: {
                    point: {type: T.xy},
                    delta: {type: T.leftTop},
                    size:  {type: T.string}
                },
                statics: {
                    _midpoint: {destroy: false}
                },
                config: {
                    css: 'circle'
                }
            });
        }

        /**
         * @override
         */
        calcMeetingPoint(point) {
            const ra = _sizeToRadius(this.size);

            const {point: pt, delta} = this;
            const {x, y} = {
                x: pt.x + delta.left,
                y: pt.y + delta.top
            };

            const dx = x - point.x;
            const dy = y - point.y;
            const dist = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));

            // size of hotspot is: radius + border; line should start right at the border (so border-w + 1)
            // see H4H.Connected._create for borderSize information
            const norm = 1 - (ra + this.borderSize + 1) / dist;

            return norm > 0
                ? {x: point.x + norm * dx, y: point.y + norm * dy}
                : point;
        }

        /**
         * get possible connection points for this control; this is an incompatible override with the base class!
         * @param {object} [params] - includes whether to use the midpoint of the circle
         * @returns {Help4.control2.ConnectionPoints} - points top,left,bottom,right and middle of this control
         */
        getConnectionPoints(params) {
            const {useMidPoint = false} = params || {};
            const {x, y} = this._midpoint;
            return !this.spotlight && useMidPoint
                ? {c: {x, y}}
                : super.getConnectionPoints(params);
        }

        /**
         *  @override
         *  @param {Help4.control2.hotspot.Circle.Params} params - same params as provided to the constructor
         */
        _onAfterInit(params) {
            super._onAfterInit(params);
            this._midpoint = {x: 0, y: 0};
        }

        /**
         * @override
         * @param {HTMLElement} dom - control DOM
         */
        _onDomCreated(dom) {
            super._onDomCreated(dom);

            this._content = this._createElement('div', {css: 'inner'});
            _apply.call(this);
        }

        /**
         * @override
         * @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
         */
        _applyPropertyToDom(event) {
            ({point: 1, delta: 1, size: 1}[event.name])
                ? _apply.call(this)
                : super._applyPropertyToDom(event);
        }

        /**
         * @override
         */
        getDragStartPosition() {
            const {point: {x, y}} = this;
            const ra = _sizeToRadius(this.size);
            return {
                x: x - ra,
                y: y - ra
            };
        }
    }

    /**
     * @memberof Help4.control2.hotspot.Circle#
     * @private
     */
    function _apply() {
        const dom = this.getDom();
        if (!dom) return;

        const {point: {x, y}, delta: {left: dx, top: dy}} = this;
        const {x: mx, y: my} = this._midpoint = {x: x + dx, y: y + dy};
        const ra = _sizeToRadius(this.size);
        const wh = ra * 2 + 'px';

        Help4.extendObject(dom.style, {
            left: mx - ra + 'px',  // left of box that contains the hotspot
            top: my - ra + 'px',  // top of ...
            width: wh,
            height: wh,
            lineHeight: wh
        });
    }

    /**
     * @memberof Help4.control2.hotspot.Circle#
     * @param {string} size
     * @returns {number}
     * @private
     */
    function _sizeToRadius(size) {
        const {HOTSPOT_SIZES} = Help4;
        return (HOTSPOT_SIZES.find(hs => hs.size === size) || HOTSPOT_SIZES[0]).radius;
    }
})();