<commit_before>/*
 * Copyright 2014 WebFilings, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(function(require) {
    'use strict';

    var _ = require('lodash');
    var DestroyUtil = require('wf-js-common/DestroyUtil');
    var DOMUtil = require('wf-js-common/DOMUtil');
    var EventSynthesizer = require('wf-js-uicomponents/awesome_map/EventSynthesizer');
    var EventTypes = require('wf-js-uicomponents/awesome_map/EventTypes');
    var InteractionSimulator = require('wf-js-uicomponents/awesome_map/InteractionSimulator');
    var Observable = require('wf-js-common/Observable');
    var TransformationQueue = require('wf-js-uicomponents/awesome_map/TransformationQueue');
    var TransformState = require('wf-js-uicomponents/awesome_map/TransformState');
    var TransformUtil = require('wf-js-uicomponents/awesome_map/TransformUtil');

    /**
     * Create a new AwesomeMap with the given configuration.
     *
     * @classdesc
     *
     * An AwesomeMap provides touch-enabled panning and zooming of content
     * via a transformation plane set within a clipping container (aka viewport).
     *
     * @name AwesomeMap
     * @constructor
     *
     * @param {HTMLElement} host
     *        The HTMLElement that hosts the AwesomeMap.
     *        NOTE: Ensure that the host has "position: relative|absolute",
     *        otherwise various dimension measurements will fail.
     *
     * @param {boolean} [options.touchScrollingEnabled=true]
     *        When touch scrolling is enabled, dragging and swiping will scroll
     *        the list and pan items. When disabled, the following events have
     *        no effect: drag, swipe, dragstart, dragend
     *
     * @example <caption>Simple Instantiation</caption>
     *
     * // AwesomeMap will provide unlimited panning and zooming by default.
     * var awesomeMap = new AwesomeMap({
     *     host: document.getElementById('host')
     * });
     *
     * @example <caption>Observing Events</caption>
     *
     * // AwesomeMaps expose a number of events that you can observe.
     * var awesomeMap = new AwesomeMap(document.getElementById('host'));
     * awesomeMap.onInteraction(function(sender, args) { });
     * awesomeMap.onInteractionStarted(function(sender, args) { });
     * awesomeMap.onInteractionFinished(function(sender, args) { });
     * awesomeMap.onScaleChanged(function(sender, args) { });
     * awesomeMap.onTransformStarted(function(sender, args) { });
     * awesomeMap.onTransformFinished(function(sender, args) { });
     * awesomeMap.onTranslationChanged(function(sender, args) { });
     *
     * @example <caption>Registering Interceptors</caption>
     *
     * // Interceptors conveniently wrap observable subscriptions into reusable behaviors.
     * // See {@link InterceptorMixin} for more information.
     * //
     * // The following AwesomeMap provides common behavior using interceptors:
     * // - {@link SwipeInterceptor} translates swipe gestures into a transformation
     * // - {@link ScaleInterceptor} limits the scale of the content to a minimum and maximum
     * // - {@link BoundaryInterceptor} applies viewport boundary constraints to the content
     * var awesomeMap = new AwesomeMap(document.getElementById('host'));
     * awesomeMap.addInterceptor(new SwipeInterceptor());
     * awesomeMap.addInterceptor(new ScaleInterceptor({ minimum: 1, maximum: 5 }));
     * awesomeMap.addInterceptor(new BoundaryInterceptor());
     *
     */
    var AwesomeMap = function(host, options) {

        /**
         * User-configurable options.
         * @type {Object}
         */
        this._options = _.extend({
            touchScrollingEnabled: true
        }, options);

        //---------------------------------------------------------
        // Observables
        //---------------------------------------------------------

        /**
         * Observable for subscribing to user interactions.
         *
         * @method AwesomeMap#onInteraction
         * @param {Function} callback
         *        Invoked with (sender, {
         *            event: {@link InteractionEvent},
         *            currentState: {@link TransformState}
         *        })
         */
        this.onInteraction = Observable.newObservable();

        /**
         * Observable for subscribing to the start of user interactions.
         * User interactions start on the capture of a touch event.
         * Mouse wheel events do not trigger this event.
         *
         * @method AwesomeMap#onInteractionStarted
         * @param {Function} callback
         *        Invoked with (sender)
         */
        this.onInteractionStarted = Observable.newObservable();

        /**
         * Observable for subscribing to the end of user interactions.
         * User interactions are deemed finished once a release event has been
         * transformed. Mouse wheel events do not trigger this event.
         *
         * @method AwesomeMap#onInteractionFinished
         * @param {Function} callback
         *        Invoked with (sender)
         */
        this.onInteractionFinished = Observable.newObservable();

        /**
         * Observable for subscribing to changes in scale.
         *
         * @method AwesomeMap#onScaleChanged
         * @param {Function} callback
         *        Invoked with (sender, {
         *            scale: {number}
         *        })
         */
        this.onScaleChanged = Observable.newObservable();

        /**
         * Observable for subscribing to the end of a transform.
         *
         * @method AwesomeMap#onTransformFinished
         * @param {Function} callback
         *        Invoked with (sender, {
         *            event: {@link InteractionEvent},
         *            finalState: {@link TransformState}
         *        })
         */
        this.onTransformFinished = Observable.newObservable();

        /**
         * Observable for subscribing to changes in content translation.
         *
         * @method AwesomeMap#onTranslationChanged
         * @param {Function} callback
         *        Invoked with (sender, {
         *            x: {number},
         *            y: {number}
         *        })
         */
        this.onTranslationChanged = Observable.newObservable();

        /**
         * Observable for subscribing to the start of a transform.
         * Subscribers receive the source interaction event and the target
         * transform state as arguments.
         *
         * @method AwesomeMap#onTransformStarted
         * @param {Function} callback
         *        Invoked with (sender, {
         *            event: {@link InteractionEvent},
         *            targetState: {@link TransformState}
         *        })
         */
        this.onTransformStarted = Observable.newObservable();

        //---------------------------------------------------------
        // Private properties
        //---------------------------------------------------------

        /**
         * Cached content dimensions that are invalidated on resize.
         * @type {Object}
         * @private
         */
        this._contentDimensions = null;

        /**
         * The current transform state of the transformation plane.
         * @type {TransformState}
         * @private
         */
        this._currentTransformState = null;

        /**
         * Custom content dimensions supplied by the consumer; useful when
         * content is dynamic and cannot be determined by inspecting the DOM.
         * @type {{ height: number, width: number }}
         * @private
         */
        this._customContentDimensions = null;

        /**
         * Whether the map will handle direct user interaction.
         * @type {Boolean}
         * @private
         */
        this._disabled = false;

        /**
         * Whether the instance is disposed.
         * @type {boolean}
         * @private
         */
        this._disposed = false;

        /**
         * Captures and synthesizes events occurring on the viewport.
         * @type {EventSynthesizer}
         * @private
         */
        this._eventSynthesizer = null;

        /**
         * The host element.
         * @type {HTMLElement}
         * @private
         */
        this._host = host;

        /**
         * Simulates interactions triggered via the public api.
         * @type {InteractionSimulator}
         * @private
         */
        this._interactionSimulator = null;

        /**
         * Collection of registered interceptors that extend default behavior.
         * @type {Array.<Object>}
         * @private
         */
        this._interceptors = [];

        /**
         * The transformation plane used to pan and zoom content.
         * @type {HTMLElement}
         * @private
         */
        this._transformationPlane = null;

        /**
         * The queue used to process events that generate transformations.
         * @type {TransformationQueue}
         * @private
         */
        this._transformationQueue = null;

        /**
         * The viewport.
         * @type {HTMLElement}
         * @private
         */
        this._viewport = null;

        /**
         * Cached viewport dimensions that are invalidated on resize.
         * @type {Object}
         * @private
         */
        this._viewportDimensions = null;

        //---------------------------------------------------------
        // Initialization
        //---------------------------------------------------------

        this._initialize();
    };

    AwesomeMap.prototype = {

        //---------------------------------------------------------
        // Public properties
        //---------------------------------------------------------

        /**
         * Gets the bounding dimensions of all the content in the AwesomeMap.
         * @method AwesomeMap#getContentDimensions
         * @returns {{
         *     width: number,
         *     height: number
         * }}
         */
        getContentDimensions: function() {
            if (this._contentDimensions) {
                return this._contentDimensions;
            }

            var customContentDimensions = this._customContentDimensions;
            var contents;
            var i;
            var length;
            var content;
            var height = 0;
            var width = 0;

            // If custom content dimensions have been configured, use them; ...
            if (customContentDimensions) {
                width = customContentDimensions.width;
                height = customContentDimensions.height;
            }
            // ... otherwise get dimensions by inspecting the DOM.
            // We want the total height and max width.
            else {
                contents = this._transformationPlane.childNodes;
                for (i = 0, length = contents.length; i < length; i++) {
                    content = contents[i];
                    width = Math.max(width, content.offsetWidth);
                    height += content.offsetHeight;
                }
            }

            // Cache the dimensions.
            this._contentDimensions = {
                width: Math.ceil(width),
                height: Math.ceil(height)
            };

            return this._contentDimensions;
        },

        /**
         * Gets the current transform state applied to the transformation plane.
         * @method AwesomeMap#getCurrentTransformState
         * @returns {TransformState}
         */
        getCurrentTransformState: function() {
            return this._currentTransformState;
        },

        /**
         * Gets the host element for this instance.
         * @param {HTMLElement} element
         */
        getHost: function() {
            return this._host;
        },

        /**
         * Gets the current scale.
         * @method AwesomeMap#getScale
         * @return {number}
         */
        getScale: function() {
            return this._currentTransformState.scale;
        },

        /**
         * Gets the transformation plane.
         * @method AwesomeMap#getTransformationPlane
         * @returns {HTMLElement}
         */
        getTransformationPlane: function() {
            return this._transformationPlane;
        },

        /**
         * Gets the current content translation.
         * @method AwesomeMap#getTranslation
         * @return {{ x: number, y: number }}
         */
        getTranslation: function() {
            return {
                x: this._currentTransformState.translateX,
                y: this._currentTransformState.translateY
            };
        },

        /**
         * Gets the viewport element.
         * @method AwesomeMap#getViewport
         * @returns {HTMLElement}
         */
        getViewport: function() {
            return this._viewport;
        },

        /**
         * Gets the dimensions of the viewport.
         * @method AwesomeMap#getViewportDimensions
         * @returns {{
         *     height: number,
         *     width: number
         * }}
         */
        getViewportDimensions: function() {
            if (this._viewportDimensions) {
                return this._viewportDimensions;
            }

            // The viewport is absolutely positioned with respect to the host element,
            // so get the style info from the host instead.
            var viewportDimensions;
            DOMUtil.makeMeasureReady(this._host, function(el) {
                var viewportStyle = window.getComputedStyle(el);
                viewportDimensions = {
                    height: parseInt(viewportStyle.height, 10),
                    width: parseInt(viewportStyle.width, 10)
                };
            });

            this._viewportDimensions = viewportDimensions;
            return viewportDimensions;
        },

        /**
         * Gets whether interactions are disabled.
         * @method AwesomeMap#isDisabled
         * @return {boolean}
         */
        isDisabled: function() {
            return this._disabled;
        },

        /**
         * Gets whether the instance is disposed.
         * @method AwesomeMap#isDisposed
         * @returns {boolean}
         */
        isDisposed: function() {
            return this._disposed;
        },

        /**
         * Gets whether a transformation is currently running.
         * @method AwesomeMap#isTransforming
         * @return {boolean}
         */
        isTransforming: function() {
            return this._transformationQueue.isProcessing();
        },

        /**
         * Sets the content dimensions to a custom value;
         * this is useful when dynamically injecting content into the map,
         * which makes it impossible to inspect the DOM to get the dimensions.
         * @method AwesomeMap#setContentDimensions
         * @param {{ height: number, width: number }} dimensions
         */
        setContentDimensions: function(dimensions) {
            this._customContentDimensions = dimensions;
            this._invalidateContentDimensions(true /* force */);
        },

        /**
         * Sets the current transform state for the map and dispatches events
         * if the scale or translation changes.
         * @method AwesomeMap#setCurrentTransformState
         * @param {TransformState} state
         */
        setCurrentTransformState: function(state) {
            var oldState = this._currentTransformState;
            this._currentTransformState = state.clone();
            if (oldState.scale !== state.scale) {
                this.onScaleChanged.dispatch([this, {
                    scale: state.scale
                }]);
            }
            if (oldState.translateX !== state.translateX ||
                oldState.translateY !== state.translateY) {
                this.onTranslationChanged.dispatch([this, {
                    x: state.translateX,
                    y: state.translateY
                }]);
            }
        },

        //---------------------------------------------------------
        // Public methods
        //---------------------------------------------------------

        /**
         * Registers an interceptor to listen for interaction events.
         * @method AwesomeMap#addInterceptor
         * @param {Object} interceptor
         */
        addInterceptor: function(interceptor) {
            interceptor.register(this);
            this._interceptors.push(interceptor);
        },

        /**
         * Appends content to the transformation plane.
         * @method AwesomeMap#appendContent
         * @param {HTMLElement} content
         */
        appendContent: function(content) {
            this._transformationPlane.appendChild(content);
            this._invalidateContentDimensions();
        },

        /**
         * Clears all content from the transformation plane.
         * @method AwesomeMap#clearContent
         */
        clearContent: function() {
            var container = this._transformationPlane;

            while (container.firstChild) {
                container.removeChild(container.firstChild);
            }

            this._invalidateContentDimensions();
        },

        /**
         * Disables direct interaction with the map.
         * @method AwesomeMap#disable
         */
        disable: function() {
            this._disabled = true;
        },

        /**
         * Disposes the instance.
         * @method AwesomeMap#dispose
         */
        dispose: function() {
            var i;
            var length;

            // Remove this from the DOM.
            this._host.removeChild(this._viewport);

            // Dispose dependencies.
            this._eventSynthesizer.dispose();
            this._interactionSimulator.dispose();

            // Dispose interceptors.
            for (i = 0, length = this._interceptors.length; i < length; i++) {
                this._interceptors[i].dispose();
            }

            // Dispose observables.
            this.onInteraction.dispose();
            this.onScaleChanged.dispose();
            this.onTransformFinished.dispose();
            this.onTransformStarted.dispose();
            this.onTranslationChanged.dispose();

            DestroyUtil.destroy(this);

            this._disposed = true;
        },

        /**
         * Enable direct interaction with the map.
         * @method AwesomeMap#enable
         */
        enable: function() {
            this._disabled = false;
        },

        /**
         * Gets a registered interceptor by type.
         * @method AwesomeMap#getInterceptor
         * @param {Type} Type
         * @returns {Object|null} The interceptor, if it exists.
         */
        getInterceptor: function(Type) {
            var i;
            var length;
            var interceptor;

            for (i = 0, length = this._interceptors.length; i < length; i++) {
                interceptor = this._interceptors[i];
                if (interceptor instanceof Type) {
                    return interceptor;
                }
            }

            return null;
        },

        /**
         * Handles interaction events published by either the event synthesizer
         * or the interaction simulator.
         * @param {Object} source - The source of the interaction event.
         * @param {InteractionEvent} args.event - The interaction event.
         * @param {Function} [args.done] - Callback invoked after the event is handled.
         */
        handleInteractionEvent: function(source, args) {
            var self = this;
            var queue = this._transformationQueue;
            var event = args.event;
            var eventType = event.type;
<commit_msg>Remove inserted line in AwesomeMap<commit_after>/*
 * Copyright 2014 WebFilings, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(function(require) {
    'use strict';

    var _ = require('lodash');
    var DestroyUtil = require('wf-js-common/DestroyUtil');
    var DOMUtil = require('wf-js-common/DOMUtil');
    var EventSynthesizer = require('wf-js-uicomponents/awesome_map/EventSynthesizer');
    var EventTypes = require('wf-js-uicomponents/awesome_map/EventTypes');
    var InteractionSimulator = require('wf-js-uicomponents/awesome_map/InteractionSimulator');
    var Observable = require('wf-js-common/Observable');
    var TransformationQueue = require('wf-js-uicomponents/awesome_map/TransformationQueue');
    var TransformState = require('wf-js-uicomponents/awesome_map/TransformState');
    var TransformUtil = require('wf-js-uicomponents/awesome_map/TransformUtil');

    /**
     * Create a new AwesomeMap with the given configuration.
     *
     * @classdesc
     *
     * An AwesomeMap provides touch-enabled panning and zooming of content
     * via a transformation plane set within a clipping container (aka viewport).
     *
     * @name AwesomeMap
     * @constructor
     *
     * @param {HTMLElement} host
     *        The HTMLElement that hosts the AwesomeMap.
     *        NOTE: Ensure that the host has "position: relative|absolute",
     *        otherwise various dimension measurements will fail.
     *
     * @param {boolean} [options.touchScrollingEnabled=true]
     *        When touch scrolling is enabled, dragging and swiping will scroll
     *        the list and pan items. When disabled, the following events have
     *        no effect: drag, swipe, dragstart, dragend
     *
     * @example <caption>Simple Instantiation</caption>
     *
     * // AwesomeMap will provide unlimited panning and zooming by default.
     * var awesomeMap = new AwesomeMap({
     *     host: document.getElementById('host')
     * });
     *
     * @example <caption>Observing Events</caption>
     *
     * // AwesomeMaps expose a number of events that you can observe.
     * var awesomeMap = new AwesomeMap(document.getElementById('host'));
     * awesomeMap.onInteraction(function(sender, args) { });
     * awesomeMap.onInteractionStarted(function(sender, args) { });
     * awesomeMap.onInteractionFinished(function(sender, args) { });
     * awesomeMap.onScaleChanged(function(sender, args) { });
     * awesomeMap.onTransformStarted(function(sender, args) { });
     * awesomeMap.onTransformFinished(function(sender, args) { });
     * awesomeMap.onTranslationChanged(function(sender, args) { });
     *
     * @example <caption>Registering Interceptors</caption>
     *
     * // Interceptors conveniently wrap observable subscriptions into reusable behaviors.
     * // See {@link InterceptorMixin} for more information.
     * //
     * // The following AwesomeMap provides common behavior using interceptors:
     * // - {@link SwipeInterceptor} translates swipe gestures into a transformation
     * // - {@link ScaleInterceptor} limits the scale of the content to a minimum and maximum
     * // - {@link BoundaryInterceptor} applies viewport boundary constraints to the content
     * var awesomeMap = new AwesomeMap(document.getElementById('host'));
     * awesomeMap.addInterceptor(new SwipeInterceptor());
     * awesomeMap.addInterceptor(new ScaleInterceptor({ minimum: 1, maximum: 5 }));
     * awesomeMap.addInterceptor(new BoundaryInterceptor());
     *
     */
    var AwesomeMap = function(host, options) {

        /**
         * User-configurable options.
         * @type {Object}
         */
        this._options = _.extend({
            touchScrollingEnabled: true
        }, options);

        //---------------------------------------------------------
        // Observables
        //---------------------------------------------------------

        /**
         * Observable for subscribing to user interactions.
         *
         * @method AwesomeMap#onInteraction
         * @param {Function} callback
         *        Invoked with (sender, {
         *            event: {@link InteractionEvent},
         *            currentState: {@link TransformState}
         *        })
         */
        this.onInteraction = Observable.newObservable();

        /**
         * Observable for subscribing to the start of user interactions.
         * User interactions start on the capture of a touch event.
         * Mouse wheel events do not trigger this event.
         *
         * @method AwesomeMap#onInteractionStarted
         * @param {Function} callback
         *        Invoked with (sender)
         */
        this.onInteractionStarted = Observable.newObservable();

        /**
         * Observable for subscribing to the end of user interactions.
         * User interactions are deemed finished once a release event has been
         * transformed. Mouse wheel events do not trigger this event.
         *
         * @method AwesomeMap#onInteractionFinished
         * @param {Function} callback
         *        Invoked with (sender)
         */
        this.onInteractionFinished = Observable.newObservable();

        /**
         * Observable for subscribing to changes in scale.
         *
         * @method AwesomeMap#onScaleChanged
         * @param {Function} callback
         *        Invoked with (sender, {
         *            scale: {number}
         *        })
         */
        this.onScaleChanged = Observable.newObservable();

        /**
         * Observable for subscribing to the end of a transform.
         *
         * @method AwesomeMap#onTransformFinished
         * @param {Function} callback
         *        Invoked with (sender, {
         *            event: {@link InteractionEvent},
         *            finalState: {@link TransformState}
         *        })
         */
        this.onTransformFinished = Observable.newObservable();

        /**
         * Observable for subscribing to changes in content translation.
         *
         * @method AwesomeMap#onTranslationChanged
         * @param {Function} callback
         *        Invoked with (sender, {
         *            x: {number},
         *            y: {number}
         *        })
         */
        this.onTranslationChanged = Observable.newObservable();

        /**
         * Observable for subscribing to the start of a transform.
         * Subscribers receive the source interaction event and the target
         * transform state as arguments.
         *
         * @method AwesomeMap#onTransformStarted
         * @param {Function} callback
         *        Invoked with (sender, {
         *            event: {@link InteractionEvent},
         *            targetState: {@link TransformState}
         *        })
         */
        this.onTransformStarted = Observable.newObservable();

        //---------------------------------------------------------
        // Private properties
        //---------------------------------------------------------

        /**
         * Cached content dimensions that are invalidated on resize.
         * @type {Object}
         * @private
         */
        this._contentDimensions = null;

        /**
         * The current transform state of the transformation plane.
         * @type {TransformState}
         * @private
         */
        this._currentTransformState = null;

        /**
         * Custom content dimensions supplied by the consumer; useful when
         * content is dynamic and cannot be determined by inspecting the DOM.
         * @type {{ height: number, width: number }}
         * @private
         */
        this._customContentDimensions = null;

        /**
         * Whether the map will handle direct user interaction.
         * @type {Boolean}
         * @private
         */
        this._disabled = false;

        /**
         * Whether the instance is disposed.
         * @type {boolean}
         * @private
         */
        this._disposed = false;

        /**
         * Captures and synthesizes events occurring on the viewport.
         * @type {EventSynthesizer}
         * @private
         */
        this._eventSynthesizer = null;

        /**
         * The host element.
         * @type {HTMLElement}
         * @private
         */
        this._host = host;

        /**
         * Simulates interactions triggered via the public api.
         * @type {InteractionSimulator}
         * @private
         */
        this._interactionSimulator = null;

        /**
         * Collection of registered interceptors that extend default behavior.
         * @type {Array.<Object>}
         * @private
         */
        this._interceptors = [];

        /**
         * The transformation plane used to pan and zoom content.
         * @type {HTMLElement}
         * @private
         */
        this._transformationPlane = null;

        /**
         * The queue used to process events that generate transformations.
         * @type {TransformationQueue}
         * @private
         */
        this._transformationQueue = null;

        /**
         * The viewport.
         * @type {HTMLElement}
         * @private
         */
        this._viewport = null;

        /**
         * Cached viewport dimensions that are invalidated on resize.
         * @type {Object}
         * @private
         */
        this._viewportDimensions = null;

        //---------------------------------------------------------
        // Initialization
        //---------------------------------------------------------

        this._initialize();
    };

    AwesomeMap.prototype = {

        //---------------------------------------------------------
        // Public properties
        //---------------------------------------------------------

        /**
         * Gets the bounding dimensions of all the content in the AwesomeMap.
         * @method AwesomeMap#getContentDimensions
         * @returns {{
         *     width: number,
         *     height: number
         * }}
         */
        getContentDimensions: function() {
            if (this._contentDimensions) {
                return this._contentDimensions;
            }

            var customContentDimensions = this._customContentDimensions;
            var contents;
            var i;
            var length;
            var content;
            var height = 0;
            var width = 0;

            // If custom content dimensions have been configured, use them; ...
            if (customContentDimensions) {
                width = customContentDimensions.width;
                height = customContentDimensions.height;
            }
            // ... otherwise get dimensions by inspecting the DOM.
            // We want the total height and max width.
            else {
                contents = this._transformationPlane.childNodes;
                for (i = 0, length = contents.length; i < length; i++) {
                    content = contents[i];
                    width = Math.max(width, content.offsetWidth);
                    height += content.offsetHeight;
                }
            }

            // Cache the dimensions.
            this._contentDimensions = {
                width: Math.ceil(width),
                height: Math.ceil(height)
            };

            return this._contentDimensions;
        },

        /**
         * Gets the current transform state applied to the transformation plane.
         * @method AwesomeMap#getCurrentTransformState
         * @returns {TransformState}
         */
        getCurrentTransformState: function() {
            return this._currentTransformState;
        },

        /**
         * Gets the host element for this instance.
         * @param {HTMLElement} element
         */
        getHost: function() {
            return this._host;
        },

        /**
         * Gets the current scale.
         * @method AwesomeMap#getScale
         * @return {number}
         */
        getScale: function() {
            return this._currentTransformState.scale;
        },

        /**
         * Gets the transformation plane.
         * @method AwesomeMap#getTransformationPlane
         * @returns {HTMLElement}
         */
        getTransformationPlane: function() {
            return this._transformationPlane;
        },

        /**
         * Gets the current content translation.
         * @method AwesomeMap#getTranslation
         * @return {{ x: number, y: number }}
         */
        getTranslation: function() {
            return {
                x: this._currentTransformState.translateX,
                y: this._currentTransformState.translateY
            };
        },

        /**
         * Gets the viewport element.
         * @method AwesomeMap#getViewport
         * @returns {HTMLElement}
         */
        getViewport: function() {
            return this._viewport;
        },

        /**
         * Gets the dimensions of the viewport.
         * @method AwesomeMap#getViewportDimensions
         * @returns {{
         *     height: number,
         *     width: number
         * }}
         */
        getViewportDimensions: function() {
            if (this._viewportDimensions) {
                return this._viewportDimensions;
            }

            // The viewport is absolutely positioned with respect to the host element,
            // so get the style info from the host instead.
            var viewportDimensions;
            DOMUtil.makeMeasureReady(this._host, function(el) {
                var viewportStyle = window.getComputedStyle(el);
                viewportDimensions = {
                    height: parseInt(viewportStyle.height, 10),
                    width: parseInt(viewportStyle.width, 10)
                };
            });

            this._viewportDimensions = viewportDimensions;
            return viewportDimensions;
        },

        /**
         * Gets whether interactions are disabled.
         * @method AwesomeMap#isDisabled
         * @return {boolean}
         */
        isDisabled: function() {
            return this._disabled;
        },

        /**
         * Gets whether the instance is disposed.
         * @method AwesomeMap#isDisposed
         * @returns {boolean}
         */
        isDisposed: function() {
            return this._disposed;
        },

        /**
         * Gets whether a transformation is currently running.
         * @method AwesomeMap#isTransforming
         * @return {boolean}
         */
        isTransforming: function() {
            return this._transformationQueue.isProcessing();
        },

        /**
         * Sets the content dimensions to a custom value;
         * this is useful when dynamically injecting content into the map,
         * which makes it impossible to inspect the DOM to get the dimensions.
         * @method AwesomeMap#setContentDimensions
         * @param {{ height: number, width: number }} dimensions
         */
        setContentDimensions: function(dimensions) {
            this._customContentDimensions = dimensions;
            this._invalidateContentDimensions(true /* force */);
        },

        /**
         * Sets the current transform state for the map and dispatches events
         * if the scale or translation changes.
         * @method AwesomeMap#setCurrentTransformState
         * @param {TransformState} state
         */
        setCurrentTransformState: function(state) {
            var oldState = this._currentTransformState;
            this._currentTransformState = state.clone();
            if (oldState.scale !== state.scale) {
                this.onScaleChanged.dispatch([this, {
                    scale: state.scale
                }]);
            }
            if (oldState.translateX !== state.translateX ||
                oldState.translateY !== state.translateY) {
                this.onTranslationChanged.dispatch([this, {
                    x: state.translateX,
                    y: state.translateY
                }]);
            }
        },

        //---------------------------------------------------------
        // Public methods
        //---------------------------------------------------------

        /**
         * Registers an interceptor to listen for interaction events.
         * @method AwesomeMap#addInterceptor
         * @param {Object} interceptor
         */
        addInterceptor: function(interceptor) {
            interceptor.register(this);
            this._interceptors.push(interceptor);
        },

        /**
         * Appends content to the transformation plane.
         * @method AwesomeMap#appendContent
         * @param {HTMLElement} content
         */
        appendContent: function(content) {
            this._transformationPlane.appendChild(content);
            this._invalidateContentDimensions();
        },

        /**
         * Clears all content from the transformation plane.
         * @method AwesomeMap#clearContent
         */
        clearContent: function() {
            var container = this._transformationPlane;

            while (container.firstChild) {
                container.removeChild(container.firstChild);
            }

            this._invalidateContentDimensions();
        },

        /**
         * Disables direct interaction with the map.
         * @method AwesomeMap#disable
         */
        disable: function() {
            this._disabled = true;
        },

        /**
         * Disposes the instance.
         * @method AwesomeMap#dispose
         */
        dispose: function() {
            var i;
            var length;

            // Remove this from the DOM.
            this._host.removeChild(this._viewport);

            // Dispose dependencies.
            this._eventSynthesizer.dispose();
            this._interactionSimulator.dispose();

            // Dispose interceptors.
            for (i = 0, length = this._interceptors.length; i < length; i++) {
                this._interceptors[i].dispose();
            }

            // Dispose observables.
            this.onInteraction.dispose();
            this.onScaleChanged.dispose();
            this.onTransformFinished.dispose();
            this.onTransformStarted.dispose();
            this.onTranslationChanged.dispose();

            DestroyUtil.destroy(this);

            this._disposed = true;
        },

        /**
         * Enable direct interaction with the map.
         * @method AwesomeMap#enable
         */
        enable: function() {
            this._disabled = false;
        },

        /**
         * Gets a registered interceptor by type.
         * @method AwesomeMap#getInterceptor
         * @param {Type} Type
         * @returns {Object|null} The interceptor, if it exists.
         */
        getInterceptor: function(Type) {
            var i;
            var length;
            var interceptor;

            for (i = 0, length = this._interceptors.length; i < length; i++) {
                interceptor = this._interceptors[i];
                if (interceptor instanceof Type) {
                    return interceptor;
                }
            }

            return null;
        },

        /**
         * Handles interaction events published by either the event synthesizer
         * or the interaction simulator.
         * @param {Object} source - The source of the interaction event.
         * @param {InteractionEvent} args.event - The interaction event.
         * @param {Function} [args.done] - Callback invoked after the event is handled.
         */
        handleInteractionEvent: function(source, args) {
            var self = this;
            var queue = this._transformationQueue;
            var event = args.event;
            var eventType = event.type;
{'fraction_non_alphanumeric': 0.11639770441948301, 'fraction_numerical': 0.0009319664492078285, 'mean_word_length': 1.5589933475586795, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1529185', 'n_tokens_mistral': 9842, 'n_tokens_neox': 9432, 'n_words': 2606}