Source: control2/Line.js

(function() {
    /**
     * @typedef {Help4.control2.Control.Params} Help4.control2.Line.Params
     * @property {Help4.control2.PositionXY} point1 - start point of line
     * @property {Help4.control2.PositionXY} point2 - end point of line
     * @property {number} [thickness = 2] - thickness of line
     * @property {string} [ending1 = 'none'] - start point design
     * @property {string} [ending2 = 'none'] - end point design
     * @property {number} [endingSize = 6] - size of start & end points
     */

    /**
     * A control to display a line.
     * @augments Help4.control2.Control
     * @property {Help4.control2.PositionXY} point1 - start point of line
     * @property {Help4.control2.PositionXY} point2 - end point of line
     * @property {number} thickness - thickness of line; readonly
     * @property {string} ending1 - start point design; readonly
     * @property {string} ending2 - end point design; readonly
     * @property {number} endingSize - size of start/end points; readonly
     */
    Help4.control2.Line = class extends Help4.control2.Control {
        /**
         * @override
         * @param {Help4.control2.Line.Params} [params]
         * @param {Help4.jscore.ControlBase.Params} [derived]
         */
        constructor(params, derived) {
            const T = Help4.jscore.ControlBase.TYPES;
            super(params, {
                params: {
                    point1:     {type: T.xy, mandatory: true},
                    point2:     {type: T.xy, mandatory: true},

                    thickness:  {type: T.number, init: 2, readonly: true},
                    ending1:    {type: T.string, init: 'none', readonly: true},  // can be round, square or none
                    ending2:    {type: T.string, init: 'none', readonly: true},
                    endingSize: {type: T.number, init: 6, readonly: true}
                },
                statics: {
                    _line: {destroy: false}
                },
                config: {
                    css: 'control-line'
                },
                derived
            });
        }

        /**
         * @override
         * @param {Help4.jscore.ControlBase.PropertyChangeEvent} event - the change event
         */
        _applyPropertyToDom({name, value, oldValue}) {
            if ({point1: 1, point2: 1}[name] >= 0) {
                _apply.call(this);
            } else {
                super._applyPropertyToDom({name, value, oldValue});
            }
        }

        /**
         * @override
         * @param {HTMLElement} dom - control DOM
         */
        _onDomCreated(dom) {
            const {endingSize, ending1, ending2, thickness} = this;

            const endingHalf = endingSize / 2;
            const endingTransform = endingHalf + 'px ' + endingHalf + 'px';

            dom.style.height = endingSize + 'px';
            dom.style.transformOrigin = endingTransform;

            // create end points
            this._createElement('div', {
                css: 'ending left ' + ending1,
                style: {
                    width: endingSize + 'px',
                    height: endingSize + 'px'
                }
            });
            this._createElement('div', {
                css: 'ending right ' + ending2,
                style: {
                    width: endingSize + 'px',
                    height: endingSize + 'px'
                }
            });

            // create line
            const padding = endingSize - thickness;
            this._line = this._createElement('div', {
                css: 'line',
                style: {
                    left: endingSize / 2 + 'px',
                    top: padding / 2 + 'px',
                    height: thickness + 'px'
                }
            });

            _apply.call(this);
        }
    }

    /**
     * @memberof Help4.control2.Line#
     * @private
     */
    function _apply() {
        const dom = this.getDom();
        const {_line, endingSize} = this;
        if (!dom || !_line) return;

        const c = _calc.call(this);
        const endingRotate = 'rotate(' + c.rot + ')';
        const endingHalf = endingSize / 2;

        // move and adjust wrapper
        const {x, y} = this.point1;
        Help4.extendObject(dom.style, {
            left: x - endingHalf + 'px',
            top: y - endingHalf + 'px',
            width: c.w + endingSize + 'px',
            transform: endingRotate
        });

        // correctly align the inner line
        _line.style.width = c.w + 'px';
    }

    /**
     * @memberof Help4.control2.Line#
     * @returns {{rot: string, w: number}}
     * @private
     */
    function _calc() {
        const {point1: {x: x1, y: y1}, point2: {x: x2, y: y2}, endingSize} = this;
        const dx = x2 - x1;
        const dy = y2 - y1;

        if (dx === 0) {
            return {
                w: Math.abs(dy) + endingSize / 2,
                rot: '270deg'
            };
        } else {
            const pi = dx > 0 ? 0 : Math.PI;
            return {
                w: Math.sqrt(dx * dx + dy * dy) + endingSize / 2,
                rot: Math.atan(dy / dx) + pi + 'rad'
            };
        }
    }
})();