<commit_before>import { DNABaseComponent } from './dna-base-component.js';

export const EXCLUDE_ON_EXTEND = [
    'name',
    'length',
    'prototype',
    'arguments',
    'caller',
    'constructor',
];

function createFunctionClass(prototype) {
    let fn = function() {};
    fn.prototype = prototype;
    return fn;
}

function defineProperties(target, props) {
    let blacklistProps = EXCLUDE_ON_EXTEND.slice(0);
    for (let i = 0; i < props.length; i++) {
        let descriptor = props[i];
        if (blacklistProps.indexOf(descriptor.key) === -1) {
            if (typeof descriptor.value === 'function') {
                target[descriptor.key] = descriptor.value;
            } else {
                descriptor.enumerable = descriptor.enumerable || false;
                descriptor.configurable = true;
                if ('value' in descriptor) {
                    descriptor.writable = true;
                }
                Object.defineProperty(target, descriptor.key, descriptor);
            }
        }
    }
}

function createClass(Constructor, protoProps, staticProps) {
    if (protoProps) {
        defineProperties(Constructor.prototype, protoProps);
    }
    if (staticProps) {
        defineProperties(Constructor, staticProps);
    }
    return Constructor;
}

function inherits(subClass, superClass) {
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true,
        },
    });
    if (superClass) {
        Object.setPrototypeOf(subClass, superClass);
    }
}

function getMethods(prototype) {
    let res = [];
    let added = EXCLUDE_ON_EXTEND.slice(0);
    function createProp(propKey) {
        if (added.indexOf(propKey) === -1) {
            let prop = {
                key: propKey,
            };
            if (typeof prototype[propKey] === 'function') {
                prop.value = prototype[propKey];
            } else {
                let descriptor = Object.getOwnPropertyDescriptor(prototype, propKey) || {};
                if (descriptor.get) {
                    prop.get = descriptor.get;
                    prop.set = descriptor.set;
                } else {
                    prop.value = prototype[propKey];
                }
            }
            added.push(propKey);
            return prop;
        }
        return false;
    }
    for (let key in prototype) {
        if (prototype.hasOwnProperty(key)) {
            let prop = createProp(key);
            if (prop) {
                res.push(prop);
            }
        }
    }
    let keys = Object.getOwnPropertyNames(prototype);
    keys.forEach((key) => {
        let prop = createProp(key);
        if (prop) {
            res.push(prop);
        }
    });
    return res;
}

function bindFN(protoFn, superFn) {
    return function(...args) {
        protoFn.apply(this, args);
        superFn.apply(this, args);
    };
}
/**
 * Convert a Class name into HTML tag.
 * @param {Class} fn Grab the tag name from this class.
 * @return {String} The tag name for the Custom Element.
 */
export function classToElement(fn) {
    let name;
    if (fn.name) {
        name = fn.name
            .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
            .replace(/^\-/, '');
    }
    return name;
}
/**
 * Get an element's property descriptor.
 * @param {Function} ctr The element constructor.
 * @param {string} prop The property to analyze.
 * @return {object} The descriptor of the property.
 */
export function getDescriptor(ctr, prop) {
    let res;
    if (ctr) {
        res = Object.getOwnPropertyDescriptor(ctr, prop);
        let proto = Object.getPrototypeOf(ctr);
        if (!res && proto) {
            res = getDescriptor(proto, prop);
        }
    }
    return res;
}
/**
 * Wrap a property descriptor get function or create a new one.
 * @param {string} prop The property to wrap.
 * @param {object} descriptor The property descriptor.
 * @return {Function} The descriptor get function wrapped.
 */
export function wrapDescriptorGet(prop, descriptor) {
    return function() {
        let res;
        if (typeof descriptor.get === 'function') {
            res = descriptor.get.call(this);
        } else {
            res = this[`__${prop}`];
        }
        return res;
    };
}
/**
 * Wrap a property descriptor set function or create a new one.
 * @param {string} prop The property to wrap.
 * @param {object} descriptor The property descriptor.
 * @param {Function} callback An optional callback to trigger on set.
 * @return {Function} The descriptor set function wrapped.
 */
export function wrapDescriptorSet(prop, descriptor, callback) {
    if (descriptor && descriptor.set && descriptor.set.callbacks) {
        descriptor.set.callbacks.push(callback);
        return descriptor.set;
    }
    let setter = function(value) {
        if (descriptor.set) {
            descriptor.set.call(this, value);
        } else {
            this[`__${prop}`] = value;
        }
        let res = this[prop];
        setter.callbacks.forEach((clb) => {
            if (typeof clb === 'function') {
                clb.call(this, prop, res);
            }
        });
        return res;
    };
    setter.callbacks = [callback];
    return setter;
}
/**
 * Convert a string from camelCase to dash-case.
 * @param {string} str The string to convert.
 * @return {string} The converted string.
 */
export function camelToDash(str) {
    return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
}
/**
 * Convert a string from dash-case to camelCase.
 * @param {string} str The string to convert.
 * @return {string} The converted string.
 */
export function dashToCamel(str) {
    return str.replace(/\W+(.)/g, (x, chr) => chr.toUpperCase());
}
/**
 * Extend a component prototype.
 * @param {function|class|object} superScope The function or the prototype to extend.
 * @param {function|class|object} subScope The function or the prototype to merge.
 * @return {function} A new extended class.
 */
export function extend(superScope, subScope) {
    if (typeof superScope !== 'function' && typeof superScope !== 'object') {
        throw new TypeError(
            `Super expression must be a function or an object, not ${typeof superScope}`
        );
    }
    let _superScope = (typeof superScope !== 'function') ?
        createFunctionClass(superScope) :
        superScope;
    let _subScope = (typeof subScope !== 'function') ?
        createFunctionClass(subScope) :
        subScope;
    let ctr = createFunctionClass({});
    inherits(ctr, _superScope);
    return createClass(ctr, getMethods(_subScope.prototype), getMethods(subScope));
}
/**
 * Normalize the `register` method arguments.
 * @param {Function|String} fn The definition or the tag name of the Custom Element.
 * @param {Object|Function} options A set of options or the Component class
                                    for the registration of the Custom Element.
 * @return {Object} A descriptor of the component with `tagName`, `config` and `scope` keys.
 */
export function digest(fn, options = {}) {
    let scope;
    let config = {};
    let tagName;
    if (typeof fn === 'string') {
        tagName = fn;
        if (typeof options === 'function') {
            scope = options;
            config = {};
        } else {
            if (typeof options === 'object') {
                config = options;
            }
            if (typeof config.prototype !== 'undefined') {
                scope = config.prototype;
            }
        }
    } else {
        scope = fn;
        if (typeof options === 'object') {
            config = options;
        }
    }
    if (typeof scope === 'function') {
        tagName = (
            tagName ||
            config.tagName ||
            (scope.hasOwnProperty('tagName') && scope.tagName) ||
            classToElement(scope)
        );
        config.prototype = scope.prototype;
        if (!config.extends && typeof scope.extends === 'string') {
            config.extends = scope.extends;
        }
    } else if (typeof scope === 'object') {
        config.prototype = scope;
        scope = function(...args) {
            config.prototype.constructor.apply(this, args);
        };
        scope.prototype = config.prototype;
    }
    if (config.prototype) {
        config.prototype = Object.create(config.prototype);
    }
    return {
        tagName,
        config,
        scope,
    };
}
/**
 * Trigger `onRegister` callbacks.
 * @param {Function|String} tagName The definition or the tag name of the Custom Element.
 * @param {Object} options A set of options for the registration of the Custom Element.
 * @return {Function} The Component constructor.
 */
export function register(fn, options = {}) {
    let pre = digest(fn, options);
    let scope = pre.scope;
    let tagName = pre.tagName;
    if (typeof scope.onRegister === 'function') {
        scope.onRegister.call(scope, tagName);
    }
    return function(element) {
        Object.setPrototypeOf(element, scope.prototype);
        element.is = tagName;
        element.createdCallback();
    };
}
/**
 * Create and register a component.
 * @param {string} fn The tag to use for the custom element. (required)
 * @param {object} options A configuration object. (`prototype` key is required)
 * @param {object} pluginOptions Some  generic replacements (optional)
 * @return {function} The Component constructor.
 */
export function create(fn, options = {}, pluginOptions = {}) {
    let pre = digest(fn, options);
    let tagName = pre.tagName;
    let config = pre.config;
    let scope = pre.scope;
    let registerFn = pluginOptions.register || register;
    let baseComponent = pluginOptions.base || DNABaseComponent;
    if (typeof tagName !== 'string') {
        throw new Error('Missing or bad typed `tagName` property');
    }
    if (typeof scope === 'undefined') {
        throw new Error('Missing prototype');
    }
    let newScope = extend(baseComponent, scope);
    for (let k in newScope.prototype) {
        if (newScope.prototype.hasOwnProperty(k)) {
            let callbacks = [
                'createdCallback',
                'attachedCallback',
                'detachedCallback',
                'attributeChangedCallback',
            ];
            if (callbacks.indexOf(k) !== -1) {
                newScope.prototype[k] = bindFN(
                    newScope.prototype[k],
                    baseComponent.prototype[k]
                );
            }
        }
    }
    for (let k in newScope) {
        if (newScope.hasOwnProperty(k)) {
            let callbacks = [
                'onRegister',
            ];
            if (callbacks.indexOf(k) !== -1) {
                newScope[k] = bindFN(
                    newScope[k],
                    baseComponent[k]
                );
            }
        }
    }
    config.prototype = newScope;
    return registerFn(tagName, config);
}
<commit_msg>fix: default register constructor initialization<commit_after>import { DNABaseComponent } from './dna-base-component.js';

export const EXCLUDE_ON_EXTEND = [
    'name',
    'length',
    'prototype',
    'arguments',
    'caller',
    'constructor',
];

function createFunctionClass(prototype) {
    let fn = function() {};
    fn.prototype = prototype;
    return fn;
}

function defineProperties(target, props) {
    let blacklistProps = EXCLUDE_ON_EXTEND.slice(0);
    for (let i = 0; i < props.length; i++) {
        let descriptor = props[i];
        if (blacklistProps.indexOf(descriptor.key) === -1) {
            if (typeof descriptor.value === 'function') {
                target[descriptor.key] = descriptor.value;
            } else {
                descriptor.enumerable = descriptor.enumerable || false;
                descriptor.configurable = true;
                if ('value' in descriptor) {
                    descriptor.writable = true;
                }
                Object.defineProperty(target, descriptor.key, descriptor);
            }
        }
    }
}

function createClass(Constructor, protoProps, staticProps) {
    if (protoProps) {
        defineProperties(Constructor.prototype, protoProps);
    }
    if (staticProps) {
        defineProperties(Constructor, staticProps);
    }
    return Constructor;
}

function inherits(subClass, superClass) {
    subClass.prototype = Object.create(superClass && superClass.prototype, {
        constructor: {
            value: subClass,
            enumerable: false,
            writable: true,
            configurable: true,
        },
    });
    if (superClass) {
        Object.setPrototypeOf(subClass, superClass);
    }
}

function getMethods(prototype) {
    let res = [];
    let added = EXCLUDE_ON_EXTEND.slice(0);
    function createProp(propKey) {
        if (added.indexOf(propKey) === -1) {
            let prop = {
                key: propKey,
            };
            if (typeof prototype[propKey] === 'function') {
                prop.value = prototype[propKey];
            } else {
                let descriptor = Object.getOwnPropertyDescriptor(prototype, propKey) || {};
                if (descriptor.get) {
                    prop.get = descriptor.get;
                    prop.set = descriptor.set;
                } else {
                    prop.value = prototype[propKey];
                }
            }
            added.push(propKey);
            return prop;
        }
        return false;
    }
    for (let key in prototype) {
        if (prototype.hasOwnProperty(key)) {
            let prop = createProp(key);
            if (prop) {
                res.push(prop);
            }
        }
    }
    let keys = Object.getOwnPropertyNames(prototype);
    keys.forEach((key) => {
        let prop = createProp(key);
        if (prop) {
            res.push(prop);
        }
    });
    return res;
}

function bindFN(protoFn, superFn) {
    return function(...args) {
        protoFn.apply(this, args);
        superFn.apply(this, args);
    };
}
/**
 * Convert a Class name into HTML tag.
 * @param {Class} fn Grab the tag name from this class.
 * @return {String} The tag name for the Custom Element.
 */
export function classToElement(fn) {
    let name;
    if (fn.name) {
        name = fn.name
            .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
            .replace(/^\-/, '');
    }
    return name;
}
/**
 * Get an element's property descriptor.
 * @param {Function} ctr The element constructor.
 * @param {string} prop The property to analyze.
 * @return {object} The descriptor of the property.
 */
export function getDescriptor(ctr, prop) {
    let res;
    if (ctr) {
        res = Object.getOwnPropertyDescriptor(ctr, prop);
        let proto = Object.getPrototypeOf(ctr);
        if (!res && proto) {
            res = getDescriptor(proto, prop);
        }
    }
    return res;
}
/**
 * Wrap a property descriptor get function or create a new one.
 * @param {string} prop The property to wrap.
 * @param {object} descriptor The property descriptor.
 * @return {Function} The descriptor get function wrapped.
 */
export function wrapDescriptorGet(prop, descriptor) {
    return function() {
        let res;
        if (typeof descriptor.get === 'function') {
            res = descriptor.get.call(this);
        } else {
            res = this[`__${prop}`];
        }
        return res;
    };
}
/**
 * Wrap a property descriptor set function or create a new one.
 * @param {string} prop The property to wrap.
 * @param {object} descriptor The property descriptor.
 * @param {Function} callback An optional callback to trigger on set.
 * @return {Function} The descriptor set function wrapped.
 */
export function wrapDescriptorSet(prop, descriptor, callback) {
    if (descriptor && descriptor.set && descriptor.set.callbacks) {
        descriptor.set.callbacks.push(callback);
        return descriptor.set;
    }
    let setter = function(value) {
        if (descriptor.set) {
            descriptor.set.call(this, value);
        } else {
            this[`__${prop}`] = value;
        }
        let res = this[prop];
        setter.callbacks.forEach((clb) => {
            if (typeof clb === 'function') {
                clb.call(this, prop, res);
            }
        });
        return res;
    };
    setter.callbacks = [callback];
    return setter;
}
/**
 * Convert a string from camelCase to dash-case.
 * @param {string} str The string to convert.
 * @return {string} The converted string.
 */
export function camelToDash(str) {
    return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
}
/**
 * Convert a string from dash-case to camelCase.
 * @param {string} str The string to convert.
 * @return {string} The converted string.
 */
export function dashToCamel(str) {
    return str.replace(/\W+(.)/g, (x, chr) => chr.toUpperCase());
}
/**
 * Extend a component prototype.
 * @param {function|class|object} superScope The function or the prototype to extend.
 * @param {function|class|object} subScope The function or the prototype to merge.
 * @return {function} A new extended class.
 */
export function extend(superScope, subScope) {
    if (typeof superScope !== 'function' && typeof superScope !== 'object') {
        throw new TypeError(
            `Super expression must be a function or an object, not ${typeof superScope}`
        );
    }
    let _superScope = (typeof superScope !== 'function') ?
        createFunctionClass(superScope) :
        superScope;
    let _subScope = (typeof subScope !== 'function') ?
        createFunctionClass(subScope) :
        subScope;
    let ctr = createFunctionClass({});
    inherits(ctr, _superScope);
    return createClass(ctr, getMethods(_subScope.prototype), getMethods(subScope));
}
/**
 * Normalize the `register` method arguments.
 * @param {Function|String} fn The definition or the tag name of the Custom Element.
 * @param {Object|Function} options A set of options or the Component class
                                    for the registration of the Custom Element.
 * @return {Object} A descriptor of the component with `tagName`, `config` and `scope` keys.
 */
export function digest(fn, options = {}) {
    let scope;
    let config = {};
    let tagName;
    if (typeof fn === 'string') {
        tagName = fn;
        if (typeof options === 'function') {
            scope = options;
            config = {};
        } else {
            if (typeof options === 'object') {
                config = options;
            }
            if (typeof config.prototype !== 'undefined') {
                scope = config.prototype;
            }
        }
    } else {
        scope = fn;
        if (typeof options === 'object') {
            config = options;
        }
    }
    if (typeof scope === 'function') {
        tagName = (
            tagName ||
            config.tagName ||
            (scope.hasOwnProperty('tagName') && scope.tagName) ||
            classToElement(scope)
        );
        config.prototype = scope.prototype;
        if (!config.extends && typeof scope.extends === 'string') {
            config.extends = scope.extends;
        }
    } else if (typeof scope === 'object') {
        config.prototype = scope;
        scope = function(...args) {
            config.prototype.constructor.apply(this, args);
        };
        scope.prototype = config.prototype;
    }
    if (config.prototype) {
        config.prototype = Object.create(config.prototype);
    }
    return {
        tagName,
        config,
        scope,
    };
}
/**
 * Trigger `onRegister` callbacks.
 * @param {Function|String} tagName The definition or the tag name of the Custom Element.
 * @param {Object} options A set of options for the registration of the Custom Element.
 * @return {Function} The Component constructor.
 */
export function register(fn, options = {}) {
    let pre = digest(fn, options);
    let scope = pre.scope;
    let tagName = pre.tagName;
    let config = pre.config;
    if (typeof scope.onRegister === 'function') {
        scope.onRegister.call(scope, tagName);
    }
    return function(element) {
        element = element || document.createElement(config.extends ? config.extends : tagName);
        Object.setPrototypeOf(element, scope.prototype);
        element.is = tagName;
        element.createdCallback();
        return element;
    };
}
/**
 * Create and register a component.
 * @param {string} fn The tag to use for the custom element. (required)
 * @param {object} options A configuration object. (`prototype` key is required)
 * @param {object} pluginOptions Some  generic replacements (optional)
 * @return {function} The Component constructor.
 */
export function create(fn, options = {}, pluginOptions = {}) {
    let pre = digest(fn, options);
    let tagName = pre.tagName;
    let config = pre.config;
    let scope = pre.scope;
    let registerFn = pluginOptions.register || register;
    let baseComponent = pluginOptions.base || DNABaseComponent;
    if (typeof tagName !== 'string') {
        throw new Error('Missing or bad typed `tagName` property');
    }
    if (typeof scope === 'undefined') {
        throw new Error('Missing prototype');
    }
    let newScope = extend(baseComponent, scope);
    for (let k in newScope.prototype) {
        if (newScope.prototype.hasOwnProperty(k)) {
            let callbacks = [
                'createdCallback',
                'attachedCallback',
                'detachedCallback',
                'attributeChangedCallback',
            ];
            if (callbacks.indexOf(k) !== -1) {
                newScope.prototype[k] = bindFN(
                    newScope.prototype[k],
                    baseComponent.prototype[k]
                );
            }
        }
    }
    for (let k in newScope) {
        if (newScope.hasOwnProperty(k)) {
            let callbacks = [
                'onRegister',
            ];
            if (callbacks.indexOf(k) !== -1) {
                newScope[k] = bindFN(
                    newScope[k],
                    baseComponent[k]
                );
            }
        }
    }
    config.prototype = newScope;
    return registerFn(tagName, config);
}
<|endoftext|>


<commit_before>'use strict';

var VisRentDistribution = Class.extend({
  init: function(divId, city_id, current_year) {
    this.container = divId;
    this.cityId = city_id;
    this.currentYear = (current_year !== undefined) ? parseInt(current_year) : null;
    this.data = null;
    this.tbiToken = window.populateData.token;
    this.rentUrl = window.populateData.endpoint + '/datasets/ds-renta-bruta-media-municipal.json?include=municipality&filter_by_province_id=' + this.cityId.slice(0, 2);
    this.popUrl = window.populateData.endpoint + '/datasets/ds-poblacion-municipal.json?filter_by_year=' + this.currentYear + '&filter_by_province_id=' + this.cityId.slice(0, 2);
    this.formatThousand = d3.format(',.0f');
    this.isMobile = window.innerWidth <= 768;

    // Set default locale
    d3.formatDefaultLocale(es_ES);

    // Chart dimensions
    this.margin = {top: 25, right: 10, bottom: 30, left: 10};
    this.width = this._width() - this.margin.left - this.margin.right;
    this.height = this._height() - this.margin.top - this.margin.bottom;

    // Scales & Ranges
    this.xScale = d3.scaleLog();
    this.yScale = d3.scaleLinear();

    this.color = d3.scaleSequential(d3.interpolatePlasma);

    // Create axes
    this.xAxis = d3.axisBottom();
    this.yAxis = d3.axisRight();

    // Chart objects
    this.svg = null;
    this.chart = null;

    // Create main elements
    this.svg = d3.select(this.container)
      .append('svg')
      .attr('width', this.width + this.margin.left + this.margin.right)
      .attr('height', this.height + this.margin.top + this.margin.bottom)
      .append('g')
      .attr('class', 'chart-container')
      .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');

    // Append axes containers
    this.svg.append('g').attr('class','x axis');
    this.svg.append('g').attr('class','y axis');

    this.tooltip = d3.select(this.container)
      .append('div')
      .attr('class', 'tooltip');

    d3.select(window).on('resize.' + this.container, this._resize.bind(this));
  },
  getData: function() {
    var rent = d3.json(this.rentUrl)
      .header('authorization', 'Bearer ' + this.tbiToken)

    var pop = d3.json(this.popUrl)
      .header('authorization', 'Bearer ' + this.tbiToken)

    d3.queue()
      .defer(rent.get)
      .defer(pop.get)
      .await(function (error, rent, pop) {
        if (error) throw error;

        rent.forEach(function(d) {
          d.rent = d.value

          delete d.value;
        });

        this.data = _(rent)
          .concat(rent, pop)
          .groupBy('location_id')
          .map(_.spread(_.merge))
          .filter('rent')
          .value();

        this.updateRender();
        this._renderCircles();
        this._renderVoronoi();
        // this._renderCityData();
      }.bind(this));
  },
  render: function() {
    if (this.data === null) {
      this.getData();
    } else {
      this.updateRender();
    }
  },
  updateRender: function(callback) {
    this.xScale
      .rangeRound([0, this.width])
      .domain(d3.extent(this.data, function(d) {return d.value}));

    this.yScale
      .rangeRound([this.height, 0])
      .domain(d3.extent(this.data, function(d) {return d.rent }));

    this.color.domain(d3.extent(this.data, function(d) {return d.rent }).reverse());

    this._renderAxis();
  },
  _renderCircles: function() {
    // We keep this separate to not create them after every resize
    var circles = this.svg.append('g')
      .attr('class', 'circles')
      .selectAll('circle')
      .data(this.data)
      .enter();

    circles.append('circle')
      .attr('class', function(d, i) { return d.location_id === +this.cityId ? 'circle' + i + ' selected-city' : 'circle' + i; }.bind(this))
      .attr('cx', function(d) { return this.xScale(d.value) }.bind(this))
      .attr('cy', function(d) { return this.yScale(d.rent) }.bind(this))
      .attr('fill', function(d) { return this.color(d.rent) }.bind(this))
      .attr('stroke', 'white')
      .attr('r', this.isMobile ? 4 : 8);

    // Add name of the current city
    this.svg.selectAll('.text-label')
      .data(this.data)
      .enter()
      .filter(function(d) { return d.location_id === +this.cityId; }.bind(this))
      .append('text')
      .attr('class', 'text-label')
      .attr('x', function(d) { return this.xScale(d.value) }.bind(this))
      .attr('y', function(d) { return this.yScale(d.rent) }.bind(this))
      .attr('dy', 6)
      .attr('dx', -15)
      .attr('text-anchor', 'end')
      .text(function(d) { return d.municipality_name });
  },
  _renderVoronoi: function() {
    // Create voronoi
    this.voronoi = d3.distanceLimitedVoronoi()
      .x(function(d) { return this.xScale(d.value); }.bind(this))
      .y(function(d) { return this.yScale(d.rent); }.bind(this))
      .limit(50)
      .extent([[0, 0], [this.width, this.height]]);

    this.voronoiGroup = this.svg.append('g')
      .attr('class', 'voronoi');

    this.voronoiGroup.selectAll('path')
      .data(this.voronoi(this.data))
      .enter()
      .append('path')
      .style('fill', 'none')
      .attr('class', 'voronoiPath')
      .attr('d', function(d) { return d.path; })
      .style('pointer-events', 'all')
      .on('mousemove', this._mousemove.bind(this))
      .on('mouseout', this._mouseout.bind(this));

    // Attach hover circle
    this.svg.append('circle')
      .style('pointer-events', 'none')
      .attr('class', 'hover')
      .attr('fill', 'none')
      .attr('transform', 'translate(-100,-100)')
      .attr('r', this.isMobile ? 4: 8);
  },
  _mousemove: function(d, i) {
    d3.select('.circle' + i).attr('stroke', 'none')

    d3.selectAll('.hover')
        .attr('stroke', '#111')
        .attr('stroke-width', 1.5)
        .attr('cx', this.xScale(d.datum.value))
        .attr('cy', this.yScale(d.datum.rent))
        .attr('transform', 'translate(0,0)');

    // // Fill the tooltip
    this.tooltip.html('
' + d.datum.municipality_name + '
' +
      '
' +
          '
' +
              'Habitantes	' +
              ''+ accounting.formatNumber(d.datum.value, 0) +'	' +
          '
' +
          '
' +
              'Renta bruta	' +
              '' + accounting.formatNumber(d.datum.rent, 0) + '€	' +
          '
' +
      '
')
      .style('opacity', 1);

    // Tooltip position
    if (this.isMobile) {
      this.tooltip.style('opacity', 0);
    } else {
      var tooltipX = (window.innerWidth / d3.event.pageX) / 2;

      this.tooltip.style('top', (d3.event.pageY + 23) + 'px');

      // Ugly tooltip hack
      if (tooltipX < 0.65) {
          return this.tooltip.style('left', (d3.event.pageX - 200) + 'px');
      } else if (tooltipX > 2.5) {
          return this.tooltip.style('left', (d3.event.pageX - 20) + 'px');
      } else {
          return this.tooltip.style('left', (d3.event.pageX - 95) + 'px');
      }
    }
  },
  _mouseout: function(d, i) {
    d3.selectAll('.circle' + i).attr('stroke', 'white');

    d3.select('.hover')
        .attr('stroke', 'none');

    this.tooltip.style('opacity', 0);
  },
  _renderAxis: function() {
    // X axis
    this.svg.select('.x.axis')
      .attr('transform', 'translate(0,' + this.height + ')');

    this.xAxis.tickPadding(5);
    this.xAxis.tickSize(0, 0);
    this.xAxis.ticks(2); // FIXME: ticks(3) creates 15 ticks
    this.xAxis.scale(this.xScale);
    this.xAxis.tickFormat(this._formatNumberX.bind(this))
    this.svg.select('.x.axis').call(this.xAxis);

    // Y axis
    this.svg.select('.y.axis')
      .attr('transform', 'translate(' + this.width + ' ,0)');

    this.yAxis.scale(this.yScale);
    this.yAxis.ticks(4);
    this.yAxis.tickSize(-this.width);
    this.yAxis.tickFormat(this._formatNumberY.bind(this));
    this.svg.select('.y.axis').call(this.yAxis);

    // Place y axis labels on top of ticks
    this.svg.selectAll('.y.axis .tick text')
      .attr('dx', '-4.5em')
      .attr('dy', '-0.55em');

    // Remove the zero on the y axis
    this.svg.selectAll('.y.axis .tick')
      .filter(function (d) { return d === 0;  })
      .remove();
  },
  _formatMillionAbbr: function(x) {
    return d3.format('.0f')(x / 1e6) + ' millón';
  },
  _formatThousandAbbr: function(x) {
    return d3.format('.0f')(x / 1e3) + ' mil';
  },
  _formatAbbreviation: function(x) {
    var v = Math.abs(x);

    return (v >= .9995e6 ? this._formatMillionAbbr : v >= .9995e4 ? this._formatThousandAbbr : this.formatThousand)(x);
  },
  _formatNumberX: function(d) {
    // Spanish custom thousand separator
    return this._formatAbbreviation(d);
  },
  _formatNumberY: function(d) {
    // Show percentages
    return accounting.formatNumber(d, 0) + '€';
  },
  _width: function() {
    return parseInt(d3.select(this.container).style('width'));
  },
  _height: function() {
    return this._width() * 0.5;
  },
  _resize: function() {
    this.width = this._width();
    this.height = this._height();

    this.updateRender();

    d3.select(this.container + ' svg')
      .attr('width', this.width + this.margin.left + this.margin.right)
      .attr('height', this.height + this.margin.top + this.margin.bottom);

    this.svg.select('.chart-container')
      .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');

    this.svg.selectAll('.circles circle')
      .attr('cx', function(d) { return this.xScale(d.value); }.bind(this))
      .attr('cy', function(d) { return this.yScale(d.rent); }.bind(this));

    this.svg.select('.text-label')
      .attr('x', function(d) { return this.xScale(d.value); }.bind(this))
      .attr('y', function(d) { return this.yScale(d.rent); }.bind(this));

    this.svg.selectAll('.rent-anno')
      .attr('x', this.width - 65);

    this.voronoi
      .x(function(d) { return this.xScale(d.value); }.bind(this))
      .y(function(d) { return this.yScale(d.rent); }.bind(this))
      .extent([[0, 0], [this.width, this.height]]);

    this.voronoiGroup.selectAll('.voronoiPath')
      .data(this.voronoi(this.data))
      .attr("d", function(d) { return d.path; });
  }
});
<commit_msg>Disable voronoi on small screens<commit_after>'use strict';

var VisRentDistribution = Class.extend({
  init: function(divId, city_id, current_year) {
    this.container = divId;
    this.cityId = city_id;
    this.currentYear = (current_year !== undefined) ? parseInt(current_year) : null;
    this.data = null;
    this.tbiToken = window.populateData.token;
    this.rentUrl = window.populateData.endpoint + '/datasets/ds-renta-bruta-media-municipal.json?include=municipality&filter_by_province_id=' + this.cityId.slice(0, 2);
    this.popUrl = window.populateData.endpoint + '/datasets/ds-poblacion-municipal.json?filter_by_year=' + this.currentYear + '&filter_by_province_id=' + this.cityId.slice(0, 2);
    this.formatThousand = d3.format(',.0f');
    this.isMobile = window.innerWidth <= 768;

    // Set default locale
    d3.formatDefaultLocale(es_ES);

    // Chart dimensions
    this.margin = {top: 25, right: 10, bottom: 30, left: 10};
    this.width = this._width() - this.margin.left - this.margin.right;
    this.height = this._height() - this.margin.top - this.margin.bottom;

    // Scales & Ranges
    this.xScale = d3.scaleLog();
    this.yScale = d3.scaleLinear();

    this.color = d3.scaleSequential(d3.interpolatePlasma);

    // Create axes
    this.xAxis = d3.axisBottom();
    this.yAxis = d3.axisRight();

    // Chart objects
    this.svg = null;
    this.chart = null;

    // Create main elements
    this.svg = d3.select(this.container)
      .append('svg')
      .attr('width', this.width + this.margin.left + this.margin.right)
      .attr('height', this.height + this.margin.top + this.margin.bottom)
      .append('g')
      .attr('class', 'chart-container')
      .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');

    // Append axes containers
    this.svg.append('g').attr('class','x axis');
    this.svg.append('g').attr('class','y axis');

    this.tooltip = d3.select(this.container)
      .append('div')
      .attr('class', 'tooltip');

    d3.select(window).on('resize.' + this.container, this._resize.bind(this));
  },
  getData: function() {
    var rent = d3.json(this.rentUrl)
      .header('authorization', 'Bearer ' + this.tbiToken)

    var pop = d3.json(this.popUrl)
      .header('authorization', 'Bearer ' + this.tbiToken)

    d3.queue()
      .defer(rent.get)
      .defer(pop.get)
      .await(function (error, rent, pop) {
        if (error) throw error;

        rent.forEach(function(d) {
          d.rent = d.value

          delete d.value;
        });

        this.data = _(rent)
          .concat(rent, pop)
          .groupBy('location_id')
          .map(_.spread(_.merge))
          .filter('rent')
          .value();

        this.updateRender();
        this._renderCircles();
        this.isMobile ? '' : this._renderVoronoi();
        // this._renderCityData();
      }.bind(this));
  },
  render: function() {
    if (this.data === null) {
      this.getData();
    } else {
      this.updateRender();
    }
  },
  updateRender: function(callback) {
    this.xScale
      .rangeRound([0, this.width])
      .domain(d3.extent(this.data, function(d) {return d.value}));

    this.yScale
      .rangeRound([this.height, 0])
      .domain(d3.extent(this.data, function(d) {return d.rent }));

    this.color.domain(d3.extent(this.data, function(d) {return d.rent }).reverse());

    this._renderAxis();
  },
  _renderCircles: function() {
    // We keep this separate to not create them after every resize
    var circles = this.svg.append('g')
      .attr('class', 'circles')
      .selectAll('circle')
      .data(this.data)
      .enter();

    circles.append('circle')
      .attr('class', function(d, i) { return d.location_id === +this.cityId ? 'circle' + i + ' selected-city' : 'circle' + i; }.bind(this))
      .attr('cx', function(d) { return this.xScale(d.value) }.bind(this))
      .attr('cy', function(d) { return this.yScale(d.rent) }.bind(this))
      .attr('fill', function(d) { return this.color(d.rent) }.bind(this))
      .attr('stroke', 'white')
      .attr('r', this.isMobile ? 4 : 8);

    // Add name of the current city
    this.svg.selectAll('.text-label')
      .data(this.data)
      .enter()
      .filter(function(d) { return d.location_id === +this.cityId; }.bind(this))
      .append('text')
      .attr('class', 'text-label')
      .attr('x', function(d) { return this.xScale(d.value) }.bind(this))
      .attr('y', function(d) { return this.yScale(d.rent) }.bind(this))
      .attr('dy', 6)
      .attr('dx', -15)
      .attr('text-anchor', 'end')
      .text(function(d) { return d.municipality_name });
  },
  _renderVoronoi: function() {
    // Create voronoi
    this.voronoi = d3.distanceLimitedVoronoi()
      .x(function(d) { return this.xScale(d.value); }.bind(this))
      .y(function(d) { return this.yScale(d.rent); }.bind(this))
      .limit(50)
      .extent([[0, 0], [this.width, this.height]]);

    this.voronoiGroup = this.svg.append('g')
      .attr('class', 'voronoi');

    this.voronoiGroup.selectAll('path')
      .data(this.voronoi(this.data))
      .enter()
      .append('path')
      .style('fill', 'none')
      .attr('class', 'voronoiPath')
      .attr('d', function(d) { return d.path; })
      .style('pointer-events', 'all')
      .on('mousemove', this._mousemove.bind(this))
      .on('mouseout', this._mouseout.bind(this));

    // Attach hover circle
    this.svg.append('circle')
      .style('pointer-events', 'none')
      .attr('class', 'hover')
      .attr('fill', 'none')
      .attr('transform', 'translate(-100,-100)')
      .attr('r', this.isMobile ? 4: 8);
  },
  _mousemove: function(d, i) {
    d3.select('.circle' + i).attr('stroke', 'none')

    d3.selectAll('.hover')
        .attr('stroke', '#111')
        .attr('stroke-width', 1.5)
        .attr('cx', this.xScale(d.datum.value))
        .attr('cy', this.yScale(d.datum.rent))
        .attr('transform', 'translate(0,0)');

    // // Fill the tooltip
    this.tooltip.html('
' + d.datum.municipality_name + '
' +
      '
' +
          '
' +
              'Habitantes	' +
              ''+ accounting.formatNumber(d.datum.value, 0) +'	' +
          '
' +
          '
' +
              'Renta bruta	' +
              '' + accounting.formatNumber(d.datum.rent, 0) + '€	' +
          '
' +
      '
')
      .style('opacity', 1);

    // Tooltip position
    if (this.isMobile) {
      this.tooltip.style('opacity', 0);
    } else {
      var tooltipX = (window.innerWidth / d3.event.pageX) / 2;

      this.tooltip.style('top', (d3.event.pageY + 23) + 'px');

      // Ugly tooltip hack
      if (tooltipX < 0.65) {
          return this.tooltip.style('left', (d3.event.pageX - 200) + 'px');
      } else if (tooltipX > 2.5) {
          return this.tooltip.style('left', (d3.event.pageX - 20) + 'px');
      } else {
          return this.tooltip.style('left', (d3.event.pageX - 95) + 'px');
      }
    }
  },
  _mouseout: function(d, i) {
    d3.selectAll('.circle' + i).attr('stroke', 'white');

    d3.select('.hover')
        .attr('stroke', 'none');

    this.tooltip.style('opacity', 0);
  },
  _renderAxis: function() {
    // X axis
    this.svg.select('.x.axis')
      .attr('transform', 'translate(0,' + this.height + ')');

    this.xAxis.tickPadding(5);
    this.xAxis.tickSize(0, 0);
    this.xAxis.ticks(2); // FIXME: ticks(3) creates 15 ticks
    this.xAxis.scale(this.xScale);
    this.xAxis.tickFormat(this._formatNumberX.bind(this))
    this.svg.select('.x.axis').call(this.xAxis);

    // Y axis
    this.svg.select('.y.axis')
      .attr('transform', 'translate(' + this.width + ' ,0)');

    this.yAxis.scale(this.yScale);
    this.yAxis.ticks(4);
    this.yAxis.tickSize(-this.width);
    this.yAxis.tickFormat(this._formatNumberY.bind(this));
    this.svg.select('.y.axis').call(this.yAxis);

    // Place y axis labels on top of ticks
    this.svg.selectAll('.y.axis .tick text')
      .attr('dx', '-4.5em')
      .attr('dy', '-0.55em');

    // Remove the zero on the y axis
    this.svg.selectAll('.y.axis .tick')
      .filter(function (d) { return d === 0;  })
      .remove();
  },
  _formatMillionAbbr: function(x) {
    return d3.format('.0f')(x / 1e6) + ' millón';
  },
  _formatThousandAbbr: function(x) {
    return d3.format('.0f')(x / 1e3) + ' mil';
  },
  _formatAbbreviation: function(x) {
    var v = Math.abs(x);

    return (v >= .9995e6 ? this._formatMillionAbbr : v >= .9995e4 ? this._formatThousandAbbr : this.formatThousand)(x);
  },
  _formatNumberX: function(d) {
    // Spanish custom thousand separator
    return this._formatAbbreviation(d);
  },
  _formatNumberY: function(d) {
    // Show percentages
    return accounting.formatNumber(d, 0) + '€';
  },
  _width: function() {
    return parseInt(d3.select(this.container).style('width'));
  },
  _height: function() {
    return this._width() * 0.5;
  },
  _resize: function() {
    this.width = this._width();
    this.height = this._height();

    this.updateRender();

    d3.select(this.container + ' svg')
      .attr('width', this.width + this.margin.left + this.margin.right)
      .attr('height', this.height + this.margin.top + this.margin.bottom);

    this.svg.select('.chart-container')
      .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')');

    this.svg.selectAll('.circles circle')
      .attr('cx', function(d) { return this.xScale(d.value); }.bind(this))
      .attr('cy', function(d) { return this.yScale(d.rent); }.bind(this));

    this.svg.select('.text-label')
      .attr('x', function(d) { return this.xScale(d.value); }.bind(this))
      .attr('y', function(d) { return this.yScale(d.rent); }.bind(this));

    this.svg.selectAll('.rent-anno')
      .attr('x', this.width - 65);

    this.voronoi
      .extent([[0, 0], [this.width, this.height]]);

    this.voronoiGroup.selectAll('.voronoiPath')
      .data(this.voronoi(this.data))
      .attr("d", function(d) { return d.path; });
  }
});
<|endoftext|>


<commit_before>module("Popcorn Base Player");
test("API", function () {

  var expects = 17,
      count = 0,
      player = Popcorn.baseplayer(),
      members = {
        'readyState' : 'number',
        'currentTime' : 'number',
        'duration' : 'number',
        'paused' : 'number',
        'ended' : 'number',
        'volume' : 'number',
        'muted' : 'number',
        'playbackRate' : 'number',
        'autoplay' : 'undefined',
        'loop' : 'undefined',
        'events' : 'object',
        'load' : 'function',
        'play' : 'function',
        'pause' : 'function',
        'timeupdate' : 'function',
        'addEventListener' : 'function',
        'dispatchEvent' : 'function'
      };

  function plus() {
    if ( ++count === expects ) {
      start();
    }
  }

  expect(expects);
  stop( 10000 );

  Popcorn.forEach( members, function ( type, prop ) {
    ok( typeof player[prop] === type, "player." + prop + " is type: " + type );
    plus();
  });
});

test("Default Functionality", function () {
  var expects = 5,
      count = 0,
      player = Popcorn.baseplayer(),
      popcorn = Popcorn( player );

  function plus() {
    if ( ++count === expects ) {
      start();
    }
  }

  expect(expects);
  stop( 1000 );

  player.addEventListener( 'timeupdate', function( evt, caller ) {
    ok( true, "Time update called" );
    plus();

    equals( player, this, "'this' is kept as player" );
    plus();

    equals( player, caller, "caller is kept as player" );
    plus();

    ok( evt instanceof window.Event, "Event object passed in" );
    plus();

    ok( evt.type === 'timeupdate', "Event object is type 'timeupdate'" );
    plus();

    player.pause();
  });

  player.play();
});

test("Extension and Method Overriding", function () {
  var expects = 3,
      count = 0,
      player = Popcorn.baseplayer(),
      playerForPopcorn = Popcorn.baseplayer(),
      popcorn;

  function plus() {
    if ( ++count === expects ) {
      start();
    }
  }

  expect(expects);
  stop( 4000 );

  Popcorn.extend( player, {
    load: function() {
      ok( true, "Load overridden" );
      plus();
    },
    timeupdate: function() {
      ok( true, "Timeupdate overridden" );
      plus();
    }
  });

  popcorn = Popcorn( playerForPopcorn )
            .exec( 2, function() {
              ok( true, "Exec triggereed from popcorn after 2 seconds" );
              plus();
            });

  player.load();
  player.play();

  playerForPopcorn.readyState = 2;
  popcorn.play();
});
<commit_msg>[#447] Unit test clarified for timeupdate overriding, event dispatching<commit_after>module("Popcorn Base Player");
test("API", function () {

  var expects = 17,
      count = 0,
      player = Popcorn.baseplayer(),
      members = {
        'readyState' : 'number',
        'currentTime' : 'number',
        'duration' : 'number',
        'paused' : 'number',
        'ended' : 'number',
        'volume' : 'number',
        'muted' : 'number',
        'playbackRate' : 'number',
        'autoplay' : 'undefined',
        'loop' : 'undefined',
        'events' : 'object',
        'load' : 'function',
        'play' : 'function',
        'pause' : 'function',
        'timeupdate' : 'function',
        'addEventListener' : 'function',
        'dispatchEvent' : 'function'
      };

  function plus() {
    if ( ++count === expects ) {
      start();
    }
  }

  expect(expects);
  stop( 10000 );

  Popcorn.forEach( members, function ( type, prop ) {
    ok( typeof player[prop] === type, "player." + prop + " is type: " + type );
    plus();
  });
});

test("Default Functionality", function () {
  var expects = 5,
      count = 0,
      player = Popcorn.baseplayer(),
      popcorn = Popcorn( player );

  function plus() {
    if ( ++count === expects ) {
      start();
    }
  }

  expect(expects);
  stop( 1000 );

  player.addEventListener( 'timeupdate', function( evt, caller ) {
    ok( true, "Time update called" );
    plus();

    equals( player, this, "'this' is kept as player" );
    plus();

    equals( player, caller, "caller is kept as player" );
    plus();

    ok( evt instanceof window.Event, "Event object passed in" );
    plus();

    ok( evt.type === 'timeupdate', "Event object is type 'timeupdate'" );
    plus();

    player.pause();
  });

  player.play();
});

test("Extension and Method Overriding", function () {
  var expects = 4,
      count = 0,
      player = Popcorn.baseplayer(),
      playerForPopcorn = Popcorn.baseplayer(),
      popcorn;

  function plus() {
    if ( ++count === expects ) {
      start();
    }
  }

  expect(expects);
  stop( 4000 );

  Popcorn.extend( player, {
    load: function() {
      ok( true, "Load overridden" );
      plus();
    },
    timeupdate: function() {
      ok( true, "Timeupdate overridden" );
      plus();

      // Must dispatch event so event listeners can work!
      this.dispatchEvent("timeupdate");

      // We don't want to cue custom timing loop using setTimeout because we only want this to run once
    }
  });

  player.addEventListener( "timeupdate", function() {
    ok( true, "Timeupdate event dispatched!" );
    plus();
  });

  popcorn = Popcorn( playerForPopcorn )
            .exec( 2, function() {
              ok( true, "Exec triggereed from popcorn after 2 seconds" );
              plus();
            });

  player.load();
  player.play();

  // Each player will define its own criteria for when readyState should be changed
  // No logic in base player for this, so we must do it manually so popcorn will be able to cue track events
  playerForPopcorn.readyState = 2;
  popcorn.play();
});
<|endoftext|>


<commit_before>(function ($) {
  // register namespace
  $.extend(true, window, {
    "Slick": {
      "RowSelectionModel": RowSelectionModel
    }
  });

  function RowSelectionModel(options) {
    var _grid;
    var _ranges = [];
    var _self = this;
    var _handler = new Slick.EventHandler();
    var _inHandler;
    var _options;
    var _defaults = {
      selectActiveRow: true
    };

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Ekohe Modify
    //   1. Add clearing row selection processing

    function init(grid) {
      _options = $.extend(true, {}, _defaults, options);
      _grid = grid;
      _handler.subscribe(_grid.onActiveCellChanged,
          wrapHandler(handleActiveCellChange));
      _handler.subscribe(_grid.onKeyDown,
          wrapHandler(handleKeyDown));
      _handler.subscribe(_grid.onClick,
          wrapHandler(handleClick));

      // Ekohe Add: Clear row selection
      $('.grid-header .selection-info').click( function() {
        _self.onSelectedRangesChanged.notify([]);
        $(this).html('');
      })
    }

    function destroy() {
      _handler.unsubscribeAll();
    }

    function wrapHandler(handler) {
      return function () {
        if (!_inHandler) {
          _inHandler = true;
          handler.apply(this, arguments);
          _inHandler = false;
        }
      };
    }

    function rangesToRows(ranges) {
      var rows = [];
      for (var i = 0; i < ranges.length; i++) {
        for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
          rows.push(j);
        }
      }
      return rows;
    }

    function rowsToRanges(rows) {
      var ranges = [];
      var lastCell = _grid.getColumns().length - 1;
      for (var i = 0; i < rows.length; i++) {
        ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
      }
      return ranges;
    }

    function getRowsRange(from, to) {
      var i, rows = [];
      for (i = from; i <= to; i++) {
        rows.push(i);
      }
      for (i = to; i < from; i++) {
        rows.push(i);
      }
      return rows;
    }

    function getSelectedRows() {
      return rangesToRows(_ranges);
    }

    function setSelectedRows(rows) {
      setSelectedRanges(rowsToRanges(rows));
    }

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Ekohe Modify
    //   1. Delete entry check of empty selection for initail toolbar status check

    function setSelectedRanges(ranges) {
      // Ekohe Delete: Also used for empty selection for initial Toolbar status
      // simle check for: empty selection didn't change, prevent firing onSelectedRangesChanged
      // if ((!_ranges || _ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
      _ranges = ranges;
      _self.onSelectedRangesChanged.notify(_ranges);
    }

    function getSelectedRanges() {
      return _ranges;
    }

    function handleActiveCellChange(e, data) {
      if (_options.selectActiveRow && data.row != null) {
        setSelectedRanges([new Slick.Range(data.row, 0, data.row, _grid.getColumns().length - 1)]);
      }
    }

    function handleKeyDown(e) {
      var activeRow = _grid.getActiveCell();
      if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which == 38 || e.which == 40)) {
        var selectedRows = getSelectedRows();
        selectedRows.sort(function (x, y) {
          return x - y
        });

        if (!selectedRows.length) {
          selectedRows = [activeRow.row];
        }

        var top = selectedRows[0];
        var bottom = selectedRows[selectedRows.length - 1];
        var active;

        if (e.which == 40) {
          active = activeRow.row < bottom || top == bottom ? ++bottom : ++top;
        } else {
          active = activeRow.row < bottom ? --bottom : --top;
        }

        if (active >= 0 && active < _grid.getDataLength()) {
          _grid.scrollRowIntoView(active);
          var tempRanges = rowsToRanges(getRowsRange(top, bottom));
          setSelectedRanges(tempRanges);
        }

        e.preventDefault();
        e.stopPropagation();
      }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Ekohe Modify
    //   1. Add data as parameter
    //   2. Add processing for single selection

    // function handleClick(e) {
    function handleClick(e, data) {
      var cell = _grid.getCellFromEvent(e);
      if (!cell || !_grid.canCellBeActive(cell.row, cell.cell)) {
        return false;
      }

      // Ekohe Delete: Support single selection
      // if (!_grid.getOptions().multiSelect || (
      //     !e.ctrlKey && !e.shiftKey && !e.metaKey)) {
      //   return false;
      // }

      var selection = rangesToRows(_ranges);
      var idx = $.inArray(cell.row, selection);

      // Ekohe Add: Processing for single selection
      if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
        handleActiveCellChange(e, data);
        return false;
      } else if (_grid.getOptions().multiSelect) {
        if (idx === -1 && (e.ctrlKey || e.metaKey)) {
          selection.push(cell.row);
          _grid.setActiveCell(cell.row, cell.cell);
        } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
          selection = $.grep(selection, function (o, i) {
            return (o !== cell.row);
          });
          _grid.setActiveCell(cell.row, cell.cell);
        } else if (selection.length && e.shiftKey) {
          var last = selection.pop();
          var from = Math.min(cell.row, last);
          var to = Math.max(cell.row, last);
          selection = [];
          for (var i = from; i <= to; i++) {
            if (i !== last) {
              selection.push(i);
            }
          }
          selection.push(last);
          _grid.setActiveCell(cell.row, cell.cell);
        }
      }

      var tempRanges = rowsToRanges(selection);
      setSelectedRanges(tempRanges);
      e.stopImmediatePropagation();

      return true;
    }

    $.extend(this, {
      "getSelectedRows": getSelectedRows,
      "setSelectedRows": setSelectedRows,

      "getSelectedRanges": getSelectedRanges,
      "setSelectedRanges": setSelectedRanges,

      "init": init,
      "destroy": destroy,

      "onSelectedRangesChanged": new Slick.Event()
    });
  }
})(jQuery);
<commit_msg>FEATURE #67027 - MD inplemetation for ToolBar: Deactive cell when clear row selection<commit_after>(function ($) {
  // register namespace
  $.extend(true, window, {
    "Slick": {
      "RowSelectionModel": RowSelectionModel
    }
  });

  function RowSelectionModel(options) {
    var _grid;
    var _ranges = [];
    var _self = this;
    var _handler = new Slick.EventHandler();
    var _inHandler;
    var _options;
    var _defaults = {
      selectActiveRow: true
    };

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Ekohe Modify
    //   1. Add clearing row selection processing

    function init(grid) {
      _options = $.extend(true, {}, _defaults, options);
      _grid = grid;
      _handler.subscribe(_grid.onActiveCellChanged,
          wrapHandler(handleActiveCellChange));
      _handler.subscribe(_grid.onKeyDown,
          wrapHandler(handleKeyDown));
      _handler.subscribe(_grid.onClick,
          wrapHandler(handleClick));

      // Ekohe Add: Clear row selection
      $('.grid-header .selection-info').click( function() {
        _self.onSelectedRangesChanged.notify([]);
        $(this).html('');
        $(grid.getActiveCellNode()).removeClass("active");
      })
    }

    function destroy() {
      _handler.unsubscribeAll();
    }

    function wrapHandler(handler) {
      return function () {
        if (!_inHandler) {
          _inHandler = true;
          handler.apply(this, arguments);
          _inHandler = false;
        }
      };
    }

    function rangesToRows(ranges) {
      var rows = [];
      for (var i = 0; i < ranges.length; i++) {
        for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
          rows.push(j);
        }
      }
      return rows;
    }

    function rowsToRanges(rows) {
      var ranges = [];
      var lastCell = _grid.getColumns().length - 1;
      for (var i = 0; i < rows.length; i++) {
        ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
      }
      return ranges;
    }

    function getRowsRange(from, to) {
      var i, rows = [];
      for (i = from; i <= to; i++) {
        rows.push(i);
      }
      for (i = to; i < from; i++) {
        rows.push(i);
      }
      return rows;
    }

    function getSelectedRows() {
      return rangesToRows(_ranges);
    }

    function setSelectedRows(rows) {
      setSelectedRanges(rowsToRanges(rows));
    }

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Ekohe Modify
    //   1. Delete entry check of empty selection for initail toolbar status check

    function setSelectedRanges(ranges) {
      // Ekohe Delete: Also used for empty selection for initial Toolbar status
      // simle check for: empty selection didn't change, prevent firing onSelectedRangesChanged
      // if ((!_ranges || _ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
      _ranges = ranges;
      _self.onSelectedRangesChanged.notify(_ranges);
    }

    function getSelectedRanges() {
      return _ranges;
    }

    function handleActiveCellChange(e, data) {
      if (_options.selectActiveRow && data.row != null) {
        setSelectedRanges([new Slick.Range(data.row, 0, data.row, _grid.getColumns().length - 1)]);
      }
    }

    function handleKeyDown(e) {
      var activeRow = _grid.getActiveCell();
      if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which == 38 || e.which == 40)) {
        var selectedRows = getSelectedRows();
        selectedRows.sort(function (x, y) {
          return x - y
        });

        if (!selectedRows.length) {
          selectedRows = [activeRow.row];
        }

        var top = selectedRows[0];
        var bottom = selectedRows[selectedRows.length - 1];
        var active;

        if (e.which == 40) {
          active = activeRow.row < bottom || top == bottom ? ++bottom : ++top;
        } else {
          active = activeRow.row < bottom ? --bottom : --top;
        }

        if (active >= 0 && active < _grid.getDataLength()) {
          _grid.scrollRowIntoView(active);
          var tempRanges = rowsToRanges(getRowsRange(top, bottom));
          setSelectedRanges(tempRanges);
        }

        e.preventDefault();
        e.stopPropagation();
      }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////
    // Ekohe Modify
    //   1. Add data as parameter
    //   2. Add processing for single selection

    // function handleClick(e) {
    function handleClick(e, data) {
      var cell = _grid.getCellFromEvent(e);
      if (!cell || !_grid.canCellBeActive(cell.row, cell.cell)) {
        return false;
      }

      // Ekohe Delete: Support single selection
      // if (!_grid.getOptions().multiSelect || (
      //     !e.ctrlKey && !e.shiftKey && !e.metaKey)) {
      //   return false;
      // }

      var selection = rangesToRows(_ranges);
      var idx = $.inArray(cell.row, selection);

      // Ekohe Add: Processing for single selection
      if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
        handleActiveCellChange(e, data);
        return false;
      } else if (_grid.getOptions().multiSelect) {
        if (idx === -1 && (e.ctrlKey || e.metaKey)) {
          selection.push(cell.row);
          _grid.setActiveCell(cell.row, cell.cell);
        } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
          selection = $.grep(selection, function (o, i) {
            return (o !== cell.row);
          });
          _grid.setActiveCell(cell.row, cell.cell);
        } else if (selection.length && e.shiftKey) {
          var last = selection.pop();
          var from = Math.min(cell.row, last);
          var to = Math.max(cell.row, last);
          selection = [];
          for (var i = from; i <= to; i++) {
            if (i !== last) {
              selection.push(i);
            }
          }
          selection.push(last);
          _grid.setActiveCell(cell.row, cell.cell);
        }
      }

      var tempRanges = rowsToRanges(selection);
      setSelectedRanges(tempRanges);
      e.stopImmediatePropagation();

      return true;
    }

    $.extend(this, {
      "getSelectedRows": getSelectedRows,
      "setSelectedRows": setSelectedRows,

      "getSelectedRanges": getSelectedRanges,
      "setSelectedRanges": setSelectedRanges,

      "init": init,
      "destroy": destroy,

      "onSelectedRangesChanged": new Slick.Event()
    });
  }
})(jQuery);
<|endoftext|>


<commit_before>"use strict";

import { CityTourMath } from "./../math";

var MapCamera = function(sceneView, initialTerrain, messageBroker) {
  var PAN_VELOCITY_DECAY = 0.875;
  var ZOOM_VELOCITY_DECAY = 0.85;
  var TILT_ROTATION_VELOCITY_DECAY = 0.85;
  var AZIMUTH_ROTATION_VELOCITY_DECAY = 0.85;
  var MINIMUM_VELOCITY = 0.00001;
  var MINIMUM_HEIGHT_OFF_GROUND = 0.416666666666667;

  var MIN_TILT_ANGLE = -Math.PI / 2;
  var MAX_TILT_ANGLE = -0.1;

  var centerOfAction;
  var zoomProperties;
  var camera = sceneView.camera();
  var terrain = initialTerrain;

  var isVelocityEnabled = false;
  var panVelocityX = 0.0;
  var panVelocityZ = 0.0;
  var zoomVelocity = 0.0;
  var azimuthRotationVelocity = 0.0;
  var tiltRotationVelocity = 0.0;

  camera.position.x = -60;
  camera.position.y = 30;
  camera.position.z = 60;
  camera.lookAt(new THREE.Vector3(0.0, 0.0, 0.0));

  var setCenterOfAction = function(newCenterOfAction) {
    centerOfAction = newCenterOfAction;
    zoomProperties = undefined;

    sceneView.centerOfActionMarkerMesh().position.set(centerOfAction.x, centerOfAction.y, centerOfAction.z);
  };

  var minimumCameraHeightAtCoordinates = function(terrain, cameraX, cameraZ) {
    var terrainHeight = Number.NEGATIVE_INFINITY;

    if (terrain !== undefined) {
      terrainHeight = terrain.heightAtCoordinates(cameraX, cameraZ);
      if (terrainHeight === undefined) {
        terrainHeight = Number.NEGATIVE_INFINITY;
      }
    }

    return terrainHeight + MINIMUM_HEIGHT_OFF_GROUND;
  };

  var pan = function(distanceX, distanceZ) {
    camera.position.x -= distanceX;
    camera.position.z -= distanceZ;
    camera.position.y = Math.max(minimumCameraHeightAtCoordinates(terrain, camera.position.x, camera.position.z), camera.position.y);

    panVelocityX = distanceX;
    panVelocityZ = distanceZ;
  };

  var calculateZoomProperties = function() {
    var cameraToCenterOfActionVector, centerOfActionPercentageOfFullHeight, zoomEndPoint;

    // Vector of camera to intersection with terrain
    cameraToCenterOfActionVector = new THREE.Vector3((camera.position.x - centerOfAction.x),
                                                     (camera.position.y - centerOfAction.y),
                                                     (camera.position.z - centerOfAction.z));

    zoomProperties = {
      cameraToCenterOfActionVector: cameraToCenterOfActionVector,
    };
  };

  var zoomTowardCenterOfAction = function(zoomDistancePercentage) {
    if (zoomProperties === undefined) {
      calculateZoomProperties();
    }

    var distanceToCenterOfAction = CityTourMath.distanceBetweenPoints3D(camera.position.x, camera.position.y, camera.position.z,
                                                                        centerOfAction.x, centerOfAction.y, centerOfAction.z);
    if (distanceToCenterOfAction <= 2.0 && zoomDistancePercentage > 0.0) {
      return;
    }
    if (distanceToCenterOfAction >= 50.0 && zoomDistancePercentage < 0.0) {
      return;
    }

    var clonedCameraToCenterOfActionVector = zoomProperties.cameraToCenterOfActionVector.clone();
    clonedCameraToCenterOfActionVector.multiplyScalar(zoomDistancePercentage);

    camera.position.x -= clonedCameraToCenterOfActionVector.x;
    camera.position.y -= clonedCameraToCenterOfActionVector.y;
    camera.position.z -= clonedCameraToCenterOfActionVector.z;
    camera.position.y = Math.max(minimumCameraHeightAtCoordinates(terrain, camera.position.x, camera.position.z), camera.position.y);
    zoomProperties.cameraToCenterOfActionVector = zoomProperties.cameraToCenterOfActionVector.clone().multiplyScalar(1.0 - zoomDistancePercentage);

    zoomVelocity = zoomDistancePercentage;
  };

  var rotateAzimuthAroundCenterOfAction = function(azimuthAngleDelta) {
    var distanceCameraToCenterOfAction = CityTourMath.distanceBetweenPoints(camera.position.x, camera.position.z, centerOfAction.x, centerOfAction.z);
    var originalAngleCameraToCenterOfAction = Math.atan2(-(camera.position.z - centerOfAction.z), camera.position.x - centerOfAction.x);
    var newAngleCameraToCenterOfAction = originalAngleCameraToCenterOfAction + azimuthAngleDelta;

    zoomProperties = undefined;

    camera.position.x = (distanceCameraToCenterOfAction * Math.cos(newAngleCameraToCenterOfAction)) + centerOfAction.x;
    camera.position.z = -(distanceCameraToCenterOfAction * Math.sin(newAngleCameraToCenterOfAction)) + centerOfAction.z;
    camera.rotation.y += azimuthAngleDelta;
    if (camera.rotation.y > Math.PI) {
      camera.rotation.y -= Math.PI * 2;
    }
    else if (camera.rotation.y < -Math.PI) {
      camera.rotation.y += Math.PI * 2;
    }

    var minimumCameraY = minimumCameraHeightAtCoordinates(terrain, camera.position.x, camera.position.z);
    if (camera.position.y < minimumCameraY) {
      camera.position.y = minimumCameraY;
      centerOfAction.y = minimumCameraY;
      setCenterOfAction(centerOfAction);
    }

    azimuthRotationVelocity = azimuthAngleDelta;

    messageBroker.publish("camera.updated", {});
  };

  var tiltCamera = function(tiltAngleDelta) {
    var distanceCameraToCenterOfAction = CityTourMath.distanceBetweenPoints3D(camera.position.x, camera.position.y, camera.position.z,
                                                                              centerOfAction.x, centerOfAction.y, centerOfAction.z);
    var newTiltAngle = CityTourMath.clamp(camera.rotation.x + tiltAngleDelta, MIN_TILT_ANGLE, MAX_TILT_ANGLE);

    var hypotenuse = distanceCameraToCenterOfAction;
    var adjacent = Math.cos(newTiltAngle) * hypotenuse;
    var opposite = -Math.sin(newTiltAngle) * hypotenuse;

    var cameraX = (adjacent * Math.sin(camera.rotation.y));
    var cameraY = opposite;
    var cameraZ = (adjacent * Math.cos(-camera.rotation.y));

    zoomProperties = undefined;

    camera.position.x = centerOfAction.x + cameraX;
    camera.position.y = centerOfAction.y + cameraY;
    camera.position.z = centerOfAction.z + cameraZ;
    camera.rotation.x = newTiltAngle;

    tiltRotationVelocity = tiltAngleDelta;

    messageBroker.publish("camera.updated", {});
  };

  var tickVelocity = function(frameCount) {
    var i;

    for (i = 0; i < frameCount; i++) {
      panVelocityX *= PAN_VELOCITY_DECAY;
      panVelocityZ *= PAN_VELOCITY_DECAY;
      zoomVelocity *= ZOOM_VELOCITY_DECAY;
      azimuthRotationVelocity *= AZIMUTH_ROTATION_VELOCITY_DECAY;
      tiltRotationVelocity *= TILT_ROTATION_VELOCITY_DECAY;

      if (Math.abs(panVelocityX) > 0.0 || Math.abs(panVelocityZ) > 0.0) {
        pan(panVelocityX, panVelocityZ);
      }

      if (Math.abs(zoomVelocity) > 0.0) {
        zoomTowardCenterOfAction(zoomVelocity);
      }

      if (Math.abs(azimuthRotationVelocity) > 0.0) {
        rotateAzimuthAroundCenterOfAction(azimuthRotationVelocity);
      }

      if (Math.abs(tiltRotationVelocity) > 0.0) {
        tiltCamera(tiltRotationVelocity);
      }
    }

    if (Math.abs(panVelocityX) < MINIMUM_VELOCITY) {
      panVelocityX = 0.0;
    }
    if (Math.abs(panVelocityZ) < MINIMUM_VELOCITY) {
      panVelocityZ = 0.0;
    }
    if (Math.abs(zoomVelocity) < MINIMUM_VELOCITY) {
      zoomVelocity = 0.0;
    }
    if (Math.abs(azimuthRotationVelocity) < MINIMUM_VELOCITY) {
      azimuthRotationVelocity = 0.0;
    }
    if (Math.abs(tiltRotationVelocity) < MINIMUM_VELOCITY) {
      tiltRotationVelocity = 0.0;
    }

    if (panVelocityX === 0.0 &&
        panVelocityZ === 0.0 &&
        zoomVelocity === 0.0 &&
        azimuthRotationVelocity === 0.0 &&
        tiltRotationVelocity === 0.0) {
      isVelocityEnabled = false;
    }
  };

  var setIsVelocityEnabled = function(newIsVelocityEnabled) {
    isVelocityEnabled = newIsVelocityEnabled;

    if (newIsVelocityEnabled === false) {
      panVelocityX = 0.0;
      panVelocityZ = 0.0;
      zoomVelocity = 0.0;
      azimuthRotationVelocity = 0.0;
      tiltRotationVelocity = 0.0;
    }
  };

  return {
    centerOfAction: function() { return centerOfAction; },
    setCenterOfAction: setCenterOfAction,
    pan: pan,
    rotateAzimuthAroundCenterOfAction: rotateAzimuthAroundCenterOfAction,
    zoomTowardCenterOfAction: zoomTowardCenterOfAction,
    tiltCamera: tiltCamera,
    isVelocityEnabled: function() { return isVelocityEnabled; },
    setIsVelocityEnabled: setIsVelocityEnabled,
    tickVelocity: tickVelocity,
    positionX: function() { return camera.position.x; },
    positionY: function() { return camera.position.y; },
    positionZ: function() { return camera.position.z; },
    azimuthAngle: function() { return camera.rotation.y; },
    tiltAngle: function() { return camera.rotation.x; },
    minTiltAngle: function() { return MIN_TILT_ANGLE; },
    maxTiltAngle: function() { return MAX_TILT_ANGLE; },
    setTerrain: function(newTerrain) { terrain = newTerrain; },
  };
};

export { MapCamera };
<commit_msg>Max zoom relative to city center, not target of zoom<commit_after>"use strict";

import { CityTourMath } from "./../math";

var MapCamera = function(sceneView, initialTerrain, messageBroker) {
  var PAN_VELOCITY_DECAY = 0.875;
  var ZOOM_VELOCITY_DECAY = 0.85;
  var TILT_ROTATION_VELOCITY_DECAY = 0.85;
  var AZIMUTH_ROTATION_VELOCITY_DECAY = 0.85;
  var MINIMUM_VELOCITY = 0.00001;
  var MINIMUM_HEIGHT_OFF_GROUND = 0.416666666666667;

  var MIN_TILT_ANGLE = -Math.PI / 2;
  var MAX_TILT_ANGLE = -0.1;

  var MAX_DISTANCE_FROM_CITY_CENTER = 200.0;

  var centerOfAction;
  var zoomProperties;
  var camera = sceneView.camera();
  var terrain = initialTerrain;

  var isVelocityEnabled = false;
  var panVelocityX = 0.0;
  var panVelocityZ = 0.0;
  var zoomVelocity = 0.0;
  var azimuthRotationVelocity = 0.0;
  var tiltRotationVelocity = 0.0;

  camera.position.x = -60;
  camera.position.y = 30;
  camera.position.z = 60;
  camera.lookAt(new THREE.Vector3(0.0, 0.0, 0.0));

  var setCenterOfAction = function(newCenterOfAction) {
    centerOfAction = newCenterOfAction;
    zoomProperties = undefined;

    sceneView.centerOfActionMarkerMesh().position.set(centerOfAction.x, centerOfAction.y, centerOfAction.z);
  };

  var minimumCameraHeightAtCoordinates = function(terrain, cameraX, cameraZ) {
    var terrainHeight = Number.NEGATIVE_INFINITY;

    if (terrain !== undefined) {
      terrainHeight = terrain.heightAtCoordinates(cameraX, cameraZ);
      if (terrainHeight === undefined) {
        terrainHeight = Number.NEGATIVE_INFINITY;
      }
    }

    return terrainHeight + MINIMUM_HEIGHT_OFF_GROUND;
  };

  var pan = function(distanceX, distanceZ) {
    camera.position.x -= distanceX;
    camera.position.z -= distanceZ;
    camera.position.y = Math.max(minimumCameraHeightAtCoordinates(terrain, camera.position.x, camera.position.z), camera.position.y);

    panVelocityX = distanceX;
    panVelocityZ = distanceZ;
  };

  var calculateZoomProperties = function() {
    var cameraToCenterOfActionVector, centerOfActionPercentageOfFullHeight, zoomEndPoint;

    // Vector of camera to intersection with terrain
    cameraToCenterOfActionVector = new THREE.Vector3((camera.position.x - centerOfAction.x),
                                                     (camera.position.y - centerOfAction.y),
                                                     (camera.position.z - centerOfAction.z));

    zoomProperties = {
      cameraToCenterOfActionVector: cameraToCenterOfActionVector,
    };
  };

  var zoomTowardCenterOfAction = function(zoomDistancePercentage) {
    if (zoomProperties === undefined) {
      calculateZoomProperties();
    }

    var distanceToCenterOfAction = CityTourMath.distanceBetweenPoints3D(camera.position.x, camera.position.y, camera.position.z,
                                                                        centerOfAction.x, centerOfAction.y, centerOfAction.z);
    var distanceToCenterOfCity = CityTourMath.distanceBetweenPoints3D(camera.position.x, camera.position.y, camera.position.z, 0.0, 0.0, 0.0);

    if (distanceToCenterOfAction <= 2.0 && zoomDistancePercentage > 0.0) {
      return;
    }
    if (distanceToCenterOfCity >= MAX_DISTANCE_FROM_CITY_CENTER && zoomDistancePercentage < 0.0) {
      return;
    }

    var clonedCameraToCenterOfActionVector = zoomProperties.cameraToCenterOfActionVector.clone();
    clonedCameraToCenterOfActionVector.multiplyScalar(zoomDistancePercentage);

    camera.position.x -= clonedCameraToCenterOfActionVector.x;
    camera.position.y -= clonedCameraToCenterOfActionVector.y;
    camera.position.z -= clonedCameraToCenterOfActionVector.z;
    camera.position.y = Math.max(minimumCameraHeightAtCoordinates(terrain, camera.position.x, camera.position.z), camera.position.y);
    zoomProperties.cameraToCenterOfActionVector = zoomProperties.cameraToCenterOfActionVector.clone().multiplyScalar(1.0 - zoomDistancePercentage);

    zoomVelocity = zoomDistancePercentage;
  };

  var rotateAzimuthAroundCenterOfAction = function(azimuthAngleDelta) {
    var distanceCameraToCenterOfAction = CityTourMath.distanceBetweenPoints(camera.position.x, camera.position.z, centerOfAction.x, centerOfAction.z);
    var originalAngleCameraToCenterOfAction = Math.atan2(-(camera.position.z - centerOfAction.z), camera.position.x - centerOfAction.x);
    var newAngleCameraToCenterOfAction = originalAngleCameraToCenterOfAction + azimuthAngleDelta;

    zoomProperties = undefined;

    camera.position.x = (distanceCameraToCenterOfAction * Math.cos(newAngleCameraToCenterOfAction)) + centerOfAction.x;
    camera.position.z = -(distanceCameraToCenterOfAction * Math.sin(newAngleCameraToCenterOfAction)) + centerOfAction.z;
    camera.rotation.y += azimuthAngleDelta;
    if (camera.rotation.y > Math.PI) {
      camera.rotation.y -= Math.PI * 2;
    }
    else if (camera.rotation.y < -Math.PI) {
      camera.rotation.y += Math.PI * 2;
    }

    var minimumCameraY = minimumCameraHeightAtCoordinates(terrain, camera.position.x, camera.position.z);
    if (camera.position.y < minimumCameraY) {
      camera.position.y = minimumCameraY;
      centerOfAction.y = minimumCameraY;
      setCenterOfAction(centerOfAction);
    }

    azimuthRotationVelocity = azimuthAngleDelta;

    messageBroker.publish("camera.updated", {});
  };

  var tiltCamera = function(tiltAngleDelta) {
    var distanceCameraToCenterOfAction = CityTourMath.distanceBetweenPoints3D(camera.position.x, camera.position.y, camera.position.z,
                                                                              centerOfAction.x, centerOfAction.y, centerOfAction.z);
    var newTiltAngle = CityTourMath.clamp(camera.rotation.x + tiltAngleDelta, MIN_TILT_ANGLE, MAX_TILT_ANGLE);

    var hypotenuse = distanceCameraToCenterOfAction;
    var adjacent = Math.cos(newTiltAngle) * hypotenuse;
    var opposite = -Math.sin(newTiltAngle) * hypotenuse;

    var cameraX = (adjacent * Math.sin(camera.rotation.y));
    var cameraY = opposite;
    var cameraZ = (adjacent * Math.cos(-camera.rotation.y));

    zoomProperties = undefined;

    camera.position.x = centerOfAction.x + cameraX;
    camera.position.y = centerOfAction.y + cameraY;
    camera.position.z = centerOfAction.z + cameraZ;
    camera.rotation.x = newTiltAngle;

    tiltRotationVelocity = tiltAngleDelta;

    messageBroker.publish("camera.updated", {});
  };

  var tickVelocity = function(frameCount) {
    var i;

    for (i = 0; i < frameCount; i++) {
      panVelocityX *= PAN_VELOCITY_DECAY;
      panVelocityZ *= PAN_VELOCITY_DECAY;
      zoomVelocity *= ZOOM_VELOCITY_DECAY;
      azimuthRotationVelocity *= AZIMUTH_ROTATION_VELOCITY_DECAY;
      tiltRotationVelocity *= TILT_ROTATION_VELOCITY_DECAY;

      if (Math.abs(panVelocityX) > 0.0 || Math.abs(panVelocityZ) > 0.0) {
        pan(panVelocityX, panVelocityZ);
      }

      if (Math.abs(zoomVelocity) > 0.0) {
        zoomTowardCenterOfAction(zoomVelocity);
      }

      if (Math.abs(azimuthRotationVelocity) > 0.0) {
        rotateAzimuthAroundCenterOfAction(azimuthRotationVelocity);
      }

      if (Math.abs(tiltRotationVelocity) > 0.0) {
        tiltCamera(tiltRotationVelocity);
      }
    }

    if (Math.abs(panVelocityX) < MINIMUM_VELOCITY) {
      panVelocityX = 0.0;
    }
    if (Math.abs(panVelocityZ) < MINIMUM_VELOCITY) {
      panVelocityZ = 0.0;
    }
    if (Math.abs(zoomVelocity) < MINIMUM_VELOCITY) {
      zoomVelocity = 0.0;
    }
    if (Math.abs(azimuthRotationVelocity) < MINIMUM_VELOCITY) {
      azimuthRotationVelocity = 0.0;
    }
    if (Math.abs(tiltRotationVelocity) < MINIMUM_VELOCITY) {
      tiltRotationVelocity = 0.0;
    }

    if (panVelocityX === 0.0 &&
        panVelocityZ === 0.0 &&
        zoomVelocity === 0.0 &&
        azimuthRotationVelocity === 0.0 &&
        tiltRotationVelocity === 0.0) {
      isVelocityEnabled = false;
    }
  };

  var setIsVelocityEnabled = function(newIsVelocityEnabled) {
    isVelocityEnabled = newIsVelocityEnabled;

    if (newIsVelocityEnabled === false) {
      panVelocityX = 0.0;
      panVelocityZ = 0.0;
      zoomVelocity = 0.0;
      azimuthRotationVelocity = 0.0;
      tiltRotationVelocity = 0.0;
    }
  };

  return {
    centerOfAction: function() { return centerOfAction; },
    setCenterOfAction: setCenterOfAction,
    pan: pan,
    rotateAzimuthAroundCenterOfAction: rotateAzimuthAroundCenterOfAction,
    zoomTowardCenterOfAction: zoomTowardCenterOfAction,
    tiltCamera: tiltCamera,
    isVelocityEnabled: function() { return isVelocityEnabled; },
    setIsVelocityEnabled: setIsVelocityEnabled,
    tickVelocity: tickVelocity,
    positionX: function() { return camera.position.x; },
    positionY: function() { return camera.position.y; },
    positionZ: function() { return camera.position.z; },
    azimuthAngle: function() { return camera.rotation.y; },
    tiltAngle: function() { return camera.rotation.x; },
    minTiltAngle: function() { return MIN_TILT_ANGLE; },
    maxTiltAngle: function() { return MAX_TILT_ANGLE; },
    setTerrain: function(newTerrain) { terrain = newTerrain; },
  };
};

export { MapCamera };
<|endoftext|>


<commit_before>"use strict";

var CityTour = CityTour || {};

CityTour.SceneView = function(containerEl, interactiveCamera, messageBroker) {
  var INTERACTIVE = 1;
  var FLYTHROUGH = 2;

  var worldData;
  var renderView = null, poleCamera;
  var sceneBuilder;
  var scene;
  var timer;
  var animationManager;
  var mode = INTERACTIVE;

  var updateCamera = function(data) {
    interactiveCamera.syncCamera(poleCamera);
    renderView.render();
  };

  var startFlythrough = function() {
    animationManager.init(worldData.centerX, worldData.centerZ, poleCamera.positionX(), poleCamera.positionY(), poleCamera.positionZ(), poleCamera.rotationX(), poleCamera.rotationY());
    timer.onTick(1);
    timer.start();
    mode = FLYTHROUGH;
    messageBroker.publish("flythrough.started", {});
  };

  var stopFlythrough = function() {
    timer.togglePause();
    interactiveCamera.syncFromPoleCamera(poleCamera);
    updateCamera();
    mode = INTERACTIVE;
    messageBroker.publish("flythrough.stopped", {});
  };

  var toggleFlythrough = function() {
    if (mode === INTERACTIVE) {
      startFlythrough();
    }
    else {
      stopFlythrough();
    }
  };

  var reset = function(newWorldConfig) {
    worldData = CityTour.WorldGenerator.generate(newWorldConfig);
    sceneBuilder = new CityTour.Scene.Builder();
    scene = sceneBuilder.build(worldData.terrain, worldData.roadNetwork, worldData.buildings);

    if (renderView === null) {
      renderView = new CityTour.RenderView(containerEl, scene);
    }
    else {
      renderView.setScene(scene);
    }

    poleCamera = renderView.poleCamera();

    timer = new CityTour.Timer();
    animationManager = new CityTour.AnimationManager(worldData.terrain, worldData.roadNetwork, poleCamera);
    timer.onTick = function(frameCount) {
      animationManager.tick(frameCount);
      renderView.render();
    };

    updateCamera();

    renderView.resize();
    renderView.render();
  };

  // See https://stackoverflow.com/questions/25126352/deallocating-buffergeometry
  var removeChildFromScene = function(obj) {
    scene.remove(obj);
    if (obj instanceof THREE.Mesh) {
      obj.geometry.dispose();
      obj.geometry = null;
      obj.material.dispose();
      obj.material = null;
    }

    obj = null;
  };

  renderView = new CityTour.RenderView(containerEl, new THREE.Scene());
  containerEl.appendChild(renderView.domElement());

  window.addEventListener('resize', renderView.resize, false);
  var id1 = messageBroker.addSubscriber("camera.updated", updateCamera);

  var destroy = function() {
    containerEl.removeChild(renderView.domElement());
    window.removeEventListener('resize', renderView.resize, false);
    messageBroker.removeSubscriber("camera.updated", id1);

    scene.children.forEach(removeChildFromScene);
  };

  return {
    reset: reset,
    toggleFlythrough: toggleFlythrough,
    domElement: function() { return renderView.domElement(); },
    destroy: destroy,
  };
};
<commit_msg>Scene is more fully disposed when old city is destroyed<commit_after>"use strict";

var CityTour = CityTour || {};

CityTour.SceneView = function(containerEl, interactiveCamera, messageBroker) {
  var INTERACTIVE = 1;
  var FLYTHROUGH = 2;

  var worldData;
  var renderView = null, poleCamera;
  var sceneBuilder;
  var scene;
  var timer;
  var animationManager;
  var mode = INTERACTIVE;

  var updateCamera = function(data) {
    interactiveCamera.syncCamera(poleCamera);
    renderView.render();
  };

  var startFlythrough = function() {
    animationManager.init(worldData.centerX, worldData.centerZ, poleCamera.positionX(), poleCamera.positionY(), poleCamera.positionZ(), poleCamera.rotationX(), poleCamera.rotationY());
    timer.onTick(1);
    timer.start();
    mode = FLYTHROUGH;
    messageBroker.publish("flythrough.started", {});
  };

  var stopFlythrough = function() {
    timer.togglePause();
    interactiveCamera.syncFromPoleCamera(poleCamera);
    updateCamera();
    mode = INTERACTIVE;
    messageBroker.publish("flythrough.stopped", {});
  };

  var toggleFlythrough = function() {
    if (mode === INTERACTIVE) {
      startFlythrough();
    }
    else {
      stopFlythrough();
    }
  };

  var reset = function(newWorldConfig) {
    worldData = CityTour.WorldGenerator.generate(newWorldConfig);
    sceneBuilder = new CityTour.Scene.Builder();
    scene = sceneBuilder.build(worldData.terrain, worldData.roadNetwork, worldData.buildings);

    if (renderView === null) {
      renderView = new CityTour.RenderView(containerEl, scene);
    }
    else {
      renderView.setScene(scene);
    }

    poleCamera = renderView.poleCamera();

    timer = new CityTour.Timer();
    animationManager = new CityTour.AnimationManager(worldData.terrain, worldData.roadNetwork, poleCamera);
    timer.onTick = function(frameCount) {
      animationManager.tick(frameCount);
      renderView.render();
    };

    updateCamera();

    renderView.resize();
    renderView.render();
  };

  // See https://stackoverflow.com/questions/25126352/deallocating-buffergeometry
  var removeChildFromScene = function(obj) {
    var i;
    for (i = obj.children.length - 1; i >= 0; i--) {
      removeChildFromScene(obj.children[i]);
    }

    scene.remove(obj);
    if (obj instanceof THREE.Mesh) {
      obj.geometry.dispose();
      obj.geometry = null;
      obj.material.dispose();
      obj.material = null;
    }

    obj = null;
  };

  renderView = new CityTour.RenderView(containerEl, new THREE.Scene());
  containerEl.appendChild(renderView.domElement());

  window.addEventListener('resize', renderView.resize, false);
  var id1 = messageBroker.addSubscriber("camera.updated", updateCamera);

  var destroy = function() {
    var i;

    containerEl.removeChild(renderView.domElement());
    window.removeEventListener('resize', renderView.resize, false);
    messageBroker.removeSubscriber("camera.updated", id1);

    for (i = scene.children.length - 1; i >= 0; i--) {
      removeChildFromScene(scene.children[i]);
    }
  };

  return {
    reset: reset,
    toggleFlythrough: toggleFlythrough,
    domElement: function() { return renderView.domElement(); },
    destroy: destroy,
  };
};
<|endoftext|>


<commit_before>const {knex} = require('../util/database').connect();
import _ from 'lodash';
import {deepChangeKeyCase} from '../util';

function getTeams(opts) {
  const isBanned = opts.client && !!opts.client.isBanned;
  const voteScoreSql = `
    (SELECT
      team_id,
      SUM(value) AS value
    FROM
      (SELECT
        wilsons(
            COUNT(CASE votes.value WHEN 1 THEN 1 ELSE null END)::int,
            COUNT(CASE votes.value WHEN -1 THEN -1 ELSE null END)::int
          ) * COUNT(votes)::numeric AS value,
        users.team_id AS team_id
      FROM feed_items
      LEFT JOIN users ON users.id = feed_items.user_id
      LEFT JOIN votes ON votes.feed_item_id = feed_items.id
      WHERE feed_items.user_id IS NOT NULL AND NOT users.is_banned
      GROUP BY feed_items.id, users.team_id
    ) AS sub_query
    GROUP BY team_id)
  `;

  const actionScoreSql = `
    (SELECT
      SUM(COALESCE(action_types.value, 0)) AS value,
      teams.id AS team_id,
      teams.name AS team_name,
      teams.image_path AS image_path,
      teams.city_id AS city_id
    FROM teams
    LEFT JOIN actions ON teams.id = actions.team_id ${isBanned ? '' : 'AND NOT actions.is_banned'}
    LEFT JOIN action_types ON actions.action_type_id = action_types.id
    GROUP BY teams.id)
  `;

  let sqlString = `
    SELECT
      actions_score.team_id AS id,
      actions_score.team_name AS name,
      actions_score.image_path,
      SUM(COALESCE(actions_score.value, 0)) + SUM(COALESCE(vote_score.value, 0)) as score,
      actions_score.city_id AS city
    FROM ${ actionScoreSql } AS actions_score
    LEFT JOIN ${ voteScoreSql } vote_score ON vote_score.team_id = actions_score.team_id
  `;

  let params = [];
  let whereClauses = [];

  if (opts.city) {
    whereClauses.push('actions_score.city_id = ?');
    params.push(opts.city);
  }

  if (whereClauses.length > 0) {
    sqlString += ` WHERE ${ whereClauses.join(' AND ')}`;
  }

  sqlString += `
    GROUP BY id, name, actions_score.image_path, city
    ORDER BY score DESC, id
  `;

  return knex.raw(sqlString, params)
  .then(result => {
    return _.map(result.rows, row => deepChangeKeyCase(row, 'camelCase'));
  });
}

export {
  getTeams
};
<commit_msg>Round score to nearest integer<commit_after>const {knex} = require('../util/database').connect();
import _ from 'lodash';
import {deepChangeKeyCase} from '../util';

function getTeams(opts) {
  const isBanned = opts.client && !!opts.client.isBanned;
  const voteScoreSql = `
    (SELECT
      team_id,
      SUM(value) AS value
    FROM
      (SELECT
        wilsons(
            COUNT(CASE votes.value WHEN 1 THEN 1 ELSE null END)::int,
            COUNT(CASE votes.value WHEN -1 THEN -1 ELSE null END)::int
          ) * COUNT(votes)::numeric AS value,
        users.team_id AS team_id
      FROM feed_items
      LEFT JOIN users ON users.id = feed_items.user_id
      LEFT JOIN votes ON votes.feed_item_id = feed_items.id
      WHERE feed_items.user_id IS NOT NULL AND NOT users.is_banned
      GROUP BY feed_items.id, users.team_id
    ) AS sub_query
    GROUP BY team_id)
  `;

  const actionScoreSql = `
    (SELECT
      SUM(COALESCE(action_types.value, 0)) AS value,
      teams.id AS team_id,
      teams.name AS team_name,
      teams.image_path AS image_path,
      teams.city_id AS city_id
    FROM teams
    LEFT JOIN actions ON teams.id = actions.team_id ${isBanned ? '' : 'AND NOT actions.is_banned'}
    LEFT JOIN action_types ON actions.action_type_id = action_types.id
    GROUP BY teams.id)
  `;

  let sqlString = `
    SELECT
      actions_score.team_id AS id,
      actions_score.team_name AS name,
      actions_score.image_path,
      ROUND(SUM(COALESCE(actions_score.value, 0)) + SUM(COALESCE(vote_score.value, 0))) as score,
      actions_score.city_id AS city
    FROM ${ actionScoreSql } AS actions_score
    LEFT JOIN ${ voteScoreSql } vote_score ON vote_score.team_id = actions_score.team_id
  `;

  let params = [];
  let whereClauses = [];

  if (opts.city) {
    whereClauses.push('actions_score.city_id = ?');
    params.push(opts.city);
  }

  if (whereClauses.length > 0) {
    sqlString += ` WHERE ${ whereClauses.join(' AND ')}`;
  }

  sqlString += `
    GROUP BY id, name, actions_score.image_path, city
    ORDER BY score DESC, id
  `;

  return knex.raw(sqlString, params)
  .then(result => {
    return _.map(result.rows, row => deepChangeKeyCase(row, 'camelCase'));
  });
}

export {
  getTeams
};
<|endoftext|>