<commit_before>const { authenticate } = require('@feathersjs/authentication').hooks;
const { BadRequest, Forbidden } = require('@feathersjs/errors');
const { hasPermission } = require('../../../hooks');

const { SC_SHORT_TITLE } = require('../../../../config/globals');

const mailContent = (firstName, lastName, registrationLink) => {
	const mail = {
		subject: `Einladung für die Nutzung der ${SC_SHORT_TITLE}!`,
		content: {
			text: `Einladung in die ${SC_SHORT_TITLE}
    Hallo ${firstName} ${lastName}!
    \\nDu wurdest eingeladen, der ${SC_SHORT_TITLE} beizutreten,
    \\nbitte vervollständige deine Registrierung unter folgendem Link: ${registrationLink}
    \\nViel Spaß und einen guten Start wünscht dir dein ${SC_SHORT_TITLE}-Team`,
			html: '',
		},
	};
	return mail;
};

const { userModel } = require('../model');

const getCurrentUserInfo = (id) => userModel.findById(id)
	.select(['schoolId', 'email', 'firstName', 'lastName', 'preferences'])
	.populate('roles')
	.lean()
	.exec();

class SendRegistrationLinkService {
	async create(data, params) {
		let totalMailsSend = 0;

		try {
			const { userIds } = data;
			let accounts = await this.app.service('/accounts').find({
				query: {
					userId: userIds,
				},
			});
			accounts = accounts.map((account) => String(account.userId));

			for (const userId of userIds) {
				// dont't send mail if user has account
				if (!accounts.includes(userId)) {
					// get user info from id in userIds
					const user = await getCurrentUserInfo(userId);

					// get registrationLink
					const { shortLink } = await this.app.service('/registrationlink')
						.create({
							role: user.roles[0],
							save: true,
							patchUser: true,
							schoolId: user.schoolId,
							toHash: user.email,
						});

					// send mail
					const { subject, content } = mailContent(user.firstName, user.lastName, shortLink);
					await this.app.service('/mails')
						.create({
							email: user.email,
							subject,
							content,
						});

					if (!(user.preferences || {}).registrationMailSend) {
						const updatedPreferences = user.preferences || {};
						updatedPreferences.registrationMailSend = true;
						await this.app.service('users')
							.patch(user._id, { preferences: updatedPreferences }, params.account);
					}
					totalMailsSend += 1;
				}
			}

			return {
				totalReceivedIds: userIds.length,
				totalMailsSend,
				alreadyRegisteredUsers: (userIds.length - totalMailsSend),
			};
		} catch (err) {
			if ((err || {}).code === 403) {
				throw new Forbidden('You have not the permission to execute this!', err);
			}
			throw new BadRequest('Can not send mail(s) with registration link', err);
		}
	}

	setup(app) {
		this.app = app;
	}
}

const SendRegistrationLinkHooks = {
	before: {
		all: [authenticate('jwt'), hasPermission(['STUDENT_LIST', 'TEACHER_LIST'])],
	},
};

module.exports = {
	Hooks: SendRegistrationLinkHooks,
	Service: SendRegistrationLinkService,
};
<commit_msg>Update MailRegistrationLink.js<commit_after>const { authenticate } = require('@feathersjs/authentication').hooks;
const { BadRequest, Forbidden } = require('@feathersjs/errors');
const { hasPermission } = require('../../../hooks');

const { SC_SHORT_TITLE } = require('../../../../config/globals');

const mailContent = (firstName, lastName, registrationLink) => {
	const mail = {
		subject: `Einladung für die Nutzung der ${SC_SHORT_TITLE}!`,
		content: {
			text: `Einladung in die ${SC_SHORT_TITLE}
    Hallo ${firstName} ${lastName}!
    \\nDu wurdest eingeladen, der ${SC_SHORT_TITLE} beizutreten,
    \\nbitte vervollständige deine Registrierung unter folgendem Link: ${registrationLink}
    \\nViel Spaß und einen guten Start wünscht dir dein ${SC_SHORT_TITLE}-Team`,
			html: '',
		},
	};
	return mail;
};

const { userModel } = require('../model');

const getCurrentUserInfo = (id) => userModel.findById(id)
	.select(['schoolId', 'email', 'firstName', 'lastName', 'preferences'])
	.populate('roles')
	.lean()
	.exec();

class SendRegistrationLinkService {
	async create(data, params) {
		let totalMailsSend = 0;

		try {
			const { userIds } = data;
			let accounts = await this.app.service('/accounts').find({
				query: {
					userId: userIds,
				},
			});
			accounts = accounts.map((account) => String(account.userId));

			for (const userId of userIds) {
				// dont't send mail if user has account
				if (!accounts.includes(userId)) {
					// get user info from id in userIds
					const user = await getCurrentUserInfo(userId);

					// get registrationLink
					const { shortLink } = await this.app.service('/registrationlink')
						.create({
							role: user.roles[0],
							save: true,
							patchUser: true,
							schoolId: user.schoolId,
							toHash: user.email,
						});

					// send mail
					const { subject, content } = mailContent(user.firstName, user.lastName, shortLink);
					await this.app.service('/mails')
						.create({
							email: user.email,
							subject,
							content,
						});

					if (!(user.preferences || {}).registrationMailSend) {
						const updatedPreferences = user.preferences || {};
						updatedPreferences.registrationMailSend = true;
						await this.app.service('users')
							.patch(user._id, { preferences: updatedPreferences }, params);
					}
					totalMailsSend += 1;
				}
			}

			return {
				totalReceivedIds: userIds.length,
				totalMailsSend,
				alreadyRegisteredUsers: (userIds.length - totalMailsSend),
			};
		} catch (err) {
			if ((err || {}).code === 403) {
				throw new Forbidden('You have not the permission to execute this!', err);
			}
			throw new BadRequest('Can not send mail(s) with registration link', err);
		}
	}

	setup(app) {
		this.app = app;
	}
}

const SendRegistrationLinkHooks = {
	before: {
		all: [authenticate('jwt'), hasPermission(['STUDENT_LIST', 'TEACHER_LIST'])],
	},
};

module.exports = {
	Hooks: SendRegistrationLinkHooks,
	Service: SendRegistrationLinkService,
};
<|endoftext|>


<commit_before>(function(angular){
	"use strict";
        angular.module("mde.swipeToRefresh")
		.directive("mdeSwipeToRefresh", mdeSwipeToRefreshDirective);

	var State = {
		None: "None",
		Updating: "Updating",
		Pulling: "Pulling"
	};

	function mdeSwipeToRefreshDirective($q, $timeout, mdeSwipeToRefreshConfig){
		return{
			restrict: "A",
            require: "?^mdeSwipeToRefreshScrollHost",
			transclude: true,
			scope:{
                mdeOnRefresh: "&?",
                mdeOnCancel: "&?",
                mdeScrollHostSelector: "@?",
                mdeThreshold: "=?"
			},
			templateUrl: "swipe-to-refresh/swipe-to-refresh.html",
			link: linkFn
		};

		function linkFn(scope, elem, attrs, mdeSwipeToRefreshScrollHost){
			elem.bind("touchstart", touchStart);
			var scrollHost, startY;
			if(mdeSwipeToRefreshScrollHost){
                scrollHost = mdeSwipeToRefreshScrollHost.getElement();
            }
            else if(scope.mdeScrollHostSelector){
			    var el = elem.parent();
			    while( el[0] != document.body){
                    el = el.parent();
                    if(el[0].matches(scope.mdeScrollHostSelector)){
                        scrollHost = el;
                        break;
                    }
                }
                // if mdeScrollHostSelector doesn't matches anything, fallback to body
                scrollHost = scrollHost || el;
            }
            else{
                // default to body
                scrollHost = angular.element(document.body);
            }
            scope.mdeThreshold = scope.mdeThreshold || mdeSwipeToRefreshConfig.threshold;

            scope.State = State;
            scope.progress = 0;
            scope.state = State.None;

            function touchStart(event){
                if(scope.state != State.None){
                    return;
                }
                startY = event.touches[0].pageY;
                elem.one("touchend", touchEnd);
                elem.bind("touchmove", touchMove);

                if(scrollHost[0].scrollTop == 0){
                    scope.progress = 0;
                    scope.movement = 0;
                    scope.$digest();
                }
            }

            function touchMove(event) {
                if(scrollHost[0].scrollTop > 0){
                    return;
                }
                var movement = event.touches[0].pageY - startY;

                if(movement > 0 && scope.state != State.Pulling){
                    $timeout(function (){
                        scope.state = State.Pulling;
                    })
                }

                if(scope.state == State.Pulling){
                    event.preventDefault();
                }

                if(movement > 0){
                    scope.movement = Math.min(calculateMovement(movement, scope.mdeThreshold));
                    scope.progress = Math.min( scope.movement/scope.mdeThreshold, 1);
                    scope.$digest();
				}
				else if(scope.movement > 0){
                    scope.movement = 0;
                    scope.progress = 0;
                    scope.$digest();
                }

            }
            function touchEnd(event){
                elem.unbind("touchmove", touchMove);
                scope.$apply(function(){
                	if(scope.progress == 1){
                        scope.state = State.Updating;
                        scope.movement = scope.mdeThreshold;
                        $q.when((scope.mdeOnRefresh || angular.noop)()).finally(function(){
                            scope.state = State.None;
                            scope.movement = 0;
                            scope.progress = 0;
                        })
                    }
                    else{
                        if(scope.movement > 0){
                            (scope.mdeOnCancel || angular.noop)();
                            scope.movement = 0;
                            scope.progress = 0;
                        }
                        $timeout(function(){
                            scope.state = State.None;
                        },100);
					}
                });
            }
		}
	}
    function calculateMovement(movement, activationThreshold) {
        /*
         These are observations on Nexus 5 for how pulling is calculated based on touch movement.
         It seems there is kind of a logarithmic relation going on (as it also feels so).
         Note that Pixels need to be divided by 3 to become comparable to web (assuming proper viewport tag exists)

         touch movement(x)	=> 	pull (y)
         72, 40
         170, 100
         240, 143
         304, 180 => near threshold
         448, 250
         592, 310
         745, 360
         904, 395
         1254, 430
         * */
        return 2 * activationThreshold * log10((movement + activationThreshold) / activationThreshold);
    }
    function log10(x){
        return Math.log(x)/Math.LN10;
        // we don't use Math.log10 as it's only available in ES6
    }
})(angular);<commit_msg>fix: start pulling from top when PTR action  is done continuously after scrolling<commit_after>(function(angular){
	"use strict";
        angular.module("mde.swipeToRefresh")
		.directive("mdeSwipeToRefresh", mdeSwipeToRefreshDirective);

	var State = {
		None: "None",
		Updating: "Updating",
		Pulling: "Pulling"
	};

	function mdeSwipeToRefreshDirective($q, $timeout, mdeSwipeToRefreshConfig){
		return{
			restrict: "A",
            require: "?^mdeSwipeToRefreshScrollHost",
			transclude: true,
			scope:{
                mdeOnRefresh: "&?",
                mdeOnCancel: "&?",
                mdeScrollHostSelector: "@?",
                mdeThreshold: "=?"
			},
			templateUrl: "swipe-to-refresh/swipe-to-refresh.html",
			link: linkFn
		};

		function linkFn(scope, elem, attrs, mdeSwipeToRefreshScrollHost){
			elem.bind("touchstart", touchStart);
			var scrollHost, startY;
			if(mdeSwipeToRefreshScrollHost){
                scrollHost = mdeSwipeToRefreshScrollHost.getElement();
            }
            else if(scope.mdeScrollHostSelector){
			    var el = elem.parent();
			    while( el[0] != document.body){
                    el = el.parent();
                    if(el[0].matches(scope.mdeScrollHostSelector)){
                        scrollHost = el;
                        break;
                    }
                }
                // if mdeScrollHostSelector doesn't matches anything, fallback to body
                scrollHost = scrollHost || el;
            }
            else{
                // default to body
                scrollHost = angular.element(document.body);
            }
            scope.mdeThreshold = scope.mdeThreshold || mdeSwipeToRefreshConfig.threshold;

            scope.State = State;
            scope.progress = 0;
            scope.state = State.None;

            function touchStart(event){
                if(scope.state != State.None){
                    return;
                }
                startY = event.touches[0].pageY;
                elem.one("touchend", touchEnd);
                elem.bind("touchmove", touchMove);

                if(scrollHost[0].scrollTop == 0){
                    scope.progress = 0;
                    scope.movement = 0;
                    scope.$digest();
                }
            }

            function touchMove(event) {
                if(scrollHost[0].scrollTop > 0){
                    return;
                }
                var movement = event.touches[0].pageY - startY;

                if(movement > 0 && scope.state != State.Pulling){
                    startY = event.touches[0].pageY;
                    movement = 0;
                    scope.state = State.Pulling;
                }

                if(scope.state == State.Pulling){
                    event.preventDefault();
                }

                if(movement > 0){
                    scope.movement = Math.min(calculateMovement(movement, scope.mdeThreshold));
                    scope.progress = Math.min( scope.movement/scope.mdeThreshold, 1);
                    scope.$digest();
				}
				else if(scope.movement > 0){
                    scope.movement = 0;
                    scope.progress = 0;
                    scope.$digest();
                }

            }
            function touchEnd(event){
                elem.unbind("touchmove", touchMove);
                scope.$apply(function(){
                	if(scope.progress == 1){
                        scope.state = State.Updating;
                        scope.movement = scope.mdeThreshold;
                        $q.when((scope.mdeOnRefresh || angular.noop)()).finally(function(){
                            scope.state = State.None;
                            scope.movement = 0;
                            scope.progress = 0;
                        })
                    }
                    else{
                        if(scope.movement > 0){
                            (scope.mdeOnCancel || angular.noop)();
                            scope.movement = 0;
                            scope.progress = 0;
                        }
                        $timeout(function(){
                            scope.state = State.None;
                        },100);
					}
                });
            }
		}
	}
    function calculateMovement(movement, activationThreshold) {
        /*
         These are observations on Nexus 5 for how pulling is calculated based on touch movement.
         It seems there is kind of a logarithmic relation going on (as it also feels so).
         Note that Pixels need to be divided by 3 to become comparable to web (assuming proper viewport tag exists)

         touch movement(x)	=> 	pull (y)
         72, 40
         170, 100
         240, 143
         304, 180 => near threshold
         448, 250
         592, 310
         745, 360
         904, 395
         1254, 430
         * */
        return 2 * activationThreshold * log10((movement + activationThreshold) / activationThreshold);
    }
    function log10(x){
        return Math.log(x)/Math.LN10;
        // we don't use Math.log10 as it's only available in ES6
    }
})(angular);<|endoftext|>


<commit_before>/*global define*/
define(['Core/JulianDate',
        'Core/TimeInterval',
        'Core/TimeIntervalCollection',
        'Core/binarySearch',
        'Core/HermitePolynomialApproximation',
        'Core/LinearApproximation',
        'Core/LagrangePolynomialApproximation'],
function(JulianDate,
        TimeInterval,
        TimeIntervalCollection,
        binarySearch,
        HermitePolynomialApproximation,
        LinearApproximation,
        LagrangePolynomialApproximation) {
    "use strict";

    var interpolators = {
        HERMITE : HermitePolynomialApproximation,
        LAGRANGE : LagrangePolynomialApproximation,
        LINEAR : LinearApproximation
    };

    function DynamicProperty(dataHandler) {
        this._dataHandler = dataHandler;
        this._intervals = new TimeIntervalCollection();
    }

    function convertDate(date, epoch) {
        if (typeof date === 'string') {
            return JulianDate.fromIso601(date);
        }
        return epoch.addSeconds(date);
    }

    DynamicProperty._mergeNewSamples = function(epoch, times, values, newData, doublesPerValue) {
        ///TODO This method can be further optimized.
        var newDataIndex = 0, i, prevItem, timesInsertionPoint, valuesInsertionPoint, timesSpliceArgs, valuesSpliceArgs, currentTime, nextTime;
        while (newDataIndex < newData.length) {
            currentTime = convertDate(newData[newDataIndex], epoch);
            timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);

            if (timesInsertionPoint < 0) {
                //Doesn't exist, insert as many additional values as we can.
                timesInsertionPoint = ~timesInsertionPoint;
                timesSpliceArgs = [timesInsertionPoint, 0];

                valuesInsertionPoint = timesInsertionPoint * doublesPerValue;
                valuesSpliceArgs = [valuesInsertionPoint, 0];
                prevItem = undefined;
                nextTime = times[timesInsertionPoint + 1];
                while (newDataIndex < newData.length) {
                    currentTime = convertDate(newData[newDataIndex], epoch);

                    //TODO We can further optimize by dealing with the special cases of === here, rather than bailing.
                    if ((typeof prevItem !== 'undefined' && JulianDate.compare(prevItem, currentTime) >= 0) || (typeof nextTime !== 'undefined' && JulianDate.compare(currentTime, nextTime) >= 0)) {
                        break;
                    }
                    timesSpliceArgs.push(currentTime);
                    newDataIndex = newDataIndex + 1;
                    for (i = 0; i < doublesPerValue; i++) {
                        valuesSpliceArgs.push(newData[newDataIndex]);
                        newDataIndex = newDataIndex + 1;
                    }
                    prevItem = currentTime;
                }

                Array.prototype.splice.apply(values, valuesSpliceArgs);
                Array.prototype.splice.apply(times, timesSpliceArgs);
            } else {
                //Found an exact match
                for (i = 0; i < doublesPerValue; i++) {
                    newDataIndex++;
                    values[(timesInsertionPoint * doublesPerValue) + i] = newData[newDataIndex];
                }
                newDataIndex++;
            }
        }
    };

    DynamicProperty.prototype.addInterval = function(czmlInterval, buffer, sourceUri) {
        var this_intervals = this._intervals;
        var this_dataHandler = this._dataHandler;
        var czmlIntervalValue = this_dataHandler.getCzmlIntervalValue(czmlInterval);

        var iso8601Interval = czmlInterval.interval;
        var intervalStart;
        var intervalStop;
        if (typeof iso8601Interval === 'undefined') {
            //FIXME, figure out how to properly handle "infinite" intervals.
            intervalStart = JulianDate.fromIso8601("0000-01-01T00:00Z");
            intervalStop = JulianDate.fromIso8601("9999-12-31T24:00Z");
        } else {
            iso8601Interval = iso8601Interval.split('/');
            intervalStart = JulianDate.fromIso8601(iso8601Interval[0]);
            intervalStop = JulianDate.fromIso8601(iso8601Interval[1]);
        }

        var existingInterval = this_intervals.findInterval(intervalStart, intervalStop);

        var intervalData;
        if (existingInterval === null) {
            intervalData = {
                interpolationAlgorithm : LinearApproximation,
                numberOfPoints : LinearApproximation.getRequiredDataPoints(1)
            };

            existingInterval = new TimeInterval(intervalStart, intervalStop, true, true);
            existingInterval.data = intervalData;
            this_intervals.addInterval(existingInterval);
        } else {
            intervalData = existingInterval.data;
        }

        var interpolationAlgorithm;
        var interpolationAlgorithmType = czmlInterval.interpolationAlgorithm;
        if (interpolationAlgorithmType) {
            interpolationAlgorithm = interpolators[interpolationAlgorithmType];
            intervalData.interpolationAlgorithm = interpolationAlgorithm;
        }
        var interpolationDegree = czmlInterval.interpolationDegree;
        if (interpolationAlgorithm && interpolationDegree) {
            intervalData.interpolationDegree = interpolationDegree;
            intervalData.numberOfPoints = interpolationAlgorithm.getRequiredDataPoints(interpolationDegree, 0);
            intervalData.xTable = undefined;
            intervalData.yTable = undefined;
        }

        if (this_dataHandler.isSampled(czmlIntervalValue)) {
            if (!intervalData.isSampled) {
                intervalData.times = [];
                intervalData.values = [];
                intervalData.isSampled = true;
            }
            var epoch = czmlInterval.epoch;
            if (typeof epoch !== 'undefined') {
                epoch = JulianDate.fromIso8601(epoch);
            }
            DynamicProperty._mergeNewSamples(epoch, intervalData.times, intervalData.values, czmlIntervalValue, this_dataHandler.doublesPerValue, JulianDate.compare);
        } else {
            //Packet itself is a constant value
            intervalData.times = undefined;
            intervalData.values = czmlIntervalValue;
            intervalData.isSampled = false;
        }
    };

    DynamicProperty.prototype.addIntervals = function(czmlIntervals, buffer, sourceUri) {
        if (Array.isArray(czmlIntervals)) {
            for ( var i = 0, len = czmlIntervals.length; i < len; i++) {
                this.addInterval(czmlIntervals[i], buffer, sourceUri);
            }
        } else {
            this.addInterval(czmlIntervals, buffer, sourceUri);
        }
    };

    DynamicProperty.prototype.getValue = function(time) {
        var this_dataHandler = this._dataHandler;
        var interval = this._intervals.findIntervalContainingDate(time);

        if (interval !== null) {
            var intervalData = interval.data;
            var times = intervalData.times;
            var values = intervalData.values;
            if (intervalData.isSampled && times.length >= 0 && values.length > 0) {
                var index = binarySearch(times, time, JulianDate.compare);

                if (index < 0) {
                    index = ~index;

                    if (index >= times.length) {
                        index = times.length - 1;
                    }

                    var firstIndex = 0;
                    var lastIndex = times.length - 1;

                    var degree = intervalData.numberOfPoints - 1;
                    var pointsInCollection = lastIndex - firstIndex + 1;

                    if (pointsInCollection < degree + 1) {
                        // Use the entire range.
                    } else {
                        var computedFirstIndex = index - ((degree / 2) | 0) - 1;
                        if (computedFirstIndex < firstIndex) {
                            computedFirstIndex = firstIndex;
                        }
                        var computedLastIndex = computedFirstIndex + degree;
                        if (computedLastIndex > lastIndex) {
                            computedLastIndex = lastIndex;
                            computedFirstIndex = computedLastIndex - degree;
                            if (computedFirstIndex < firstIndex) {
                                computedFirstIndex = firstIndex;
                            }
                        }

                        firstIndex = computedFirstIndex;
                        lastIndex = computedLastIndex;
                    }

                    var length = lastIndex - firstIndex + 1;

                    var doublesPerInterpolationValue = this_dataHandler.doublesPerInterpolationValue, xTable = intervalData.xTable, yTable = intervalData.yTable;

                    if (typeof xTable === 'undefined') {
                        xTable = intervalData.xTable = new Array(intervalData.numberOfPoints);
                        yTable = intervalData.yTable = new Array(intervalData.numberOfPoints * doublesPerInterpolationValue);
                    }

                    // Build the tables
                    for ( var i = 0; i < length; ++i) {
                        xTable[i] = times[lastIndex].getSecondsDifference(times[firstIndex + i]);
                    }
                    this_dataHandler.packValuesForInterpolation(values, yTable, firstIndex, lastIndex);

                    // Interpolate!
                    var x = times[lastIndex].getSecondsDifference(time);
                    var interpolationFunction = intervalData.interpolationAlgorithm.interpolateOrderZero;
                    var result = interpolationFunction(x, xTable, yTable, doublesPerInterpolationValue);

                    return this_dataHandler.createValueFromInterpolationResult(result, values, firstIndex, lastIndex);
                }
                return this_dataHandler.createValueFromArray(intervalData.values, index * this_dataHandler.doublesPerValue);
            }
            return this_dataHandler.createValue(intervalData.values);
        }
        return undefined;
    };

    return DynamicProperty;
});<commit_msg>Have DynamicProperty expose a cacheable property indicating if getValue will always return the same result.<commit_after>/*global define*/
define(['Core/JulianDate',
        'Core/TimeInterval',
        'Core/TimeIntervalCollection',
        'Core/binarySearch',
        'Core/HermitePolynomialApproximation',
        'Core/LinearApproximation',
        'Core/LagrangePolynomialApproximation'],
function(JulianDate,
        TimeInterval,
        TimeIntervalCollection,
        binarySearch,
        HermitePolynomialApproximation,
        LinearApproximation,
        LagrangePolynomialApproximation) {
    "use strict";

    var interpolators = {
        HERMITE : HermitePolynomialApproximation,
        LAGRANGE : LagrangePolynomialApproximation,
        LINEAR : LinearApproximation
    };

    function DynamicProperty(dataHandler) {
        this._dataHandler = dataHandler;
        this._intervals = new TimeIntervalCollection();
        this.cacheable = true;
    }

    function convertDate(date, epoch) {
        if (typeof date === 'string') {
            return JulianDate.fromIso601(date);
        }
        return epoch.addSeconds(date);
    }

    DynamicProperty._mergeNewSamples = function(epoch, times, values, newData, doublesPerValue) {
        ///TODO This method can be further optimized.
        var newDataIndex = 0, i, prevItem, timesInsertionPoint, valuesInsertionPoint, timesSpliceArgs, valuesSpliceArgs, currentTime, nextTime;
        while (newDataIndex < newData.length) {
            currentTime = convertDate(newData[newDataIndex], epoch);
            timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);

            if (timesInsertionPoint < 0) {
                //Doesn't exist, insert as many additional values as we can.
                timesInsertionPoint = ~timesInsertionPoint;
                timesSpliceArgs = [timesInsertionPoint, 0];

                valuesInsertionPoint = timesInsertionPoint * doublesPerValue;
                valuesSpliceArgs = [valuesInsertionPoint, 0];
                prevItem = undefined;
                nextTime = times[timesInsertionPoint + 1];
                while (newDataIndex < newData.length) {
                    currentTime = convertDate(newData[newDataIndex], epoch);

                    //TODO We can further optimize by dealing with the special cases of === here, rather than bailing.
                    if ((typeof prevItem !== 'undefined' && JulianDate.compare(prevItem, currentTime) >= 0) || (typeof nextTime !== 'undefined' && JulianDate.compare(currentTime, nextTime) >= 0)) {
                        break;
                    }
                    timesSpliceArgs.push(currentTime);
                    newDataIndex = newDataIndex + 1;
                    for (i = 0; i < doublesPerValue; i++) {
                        valuesSpliceArgs.push(newData[newDataIndex]);
                        newDataIndex = newDataIndex + 1;
                    }
                    prevItem = currentTime;
                }

                Array.prototype.splice.apply(values, valuesSpliceArgs);
                Array.prototype.splice.apply(times, timesSpliceArgs);
            } else {
                //Found an exact match
                for (i = 0; i < doublesPerValue; i++) {
                    newDataIndex++;
                    values[(timesInsertionPoint * doublesPerValue) + i] = newData[newDataIndex];
                }
                newDataIndex++;
            }
        }
    };

    DynamicProperty.prototype.addInterval = function(czmlInterval, buffer, sourceUri) {
        var this_intervals = this._intervals;
        var this_dataHandler = this._dataHandler;
        var czmlIntervalValue = this_dataHandler.getCzmlIntervalValue(czmlInterval);

        var iso8601Interval = czmlInterval.interval;
        var intervalStart;
        var intervalStop;
        if (typeof iso8601Interval === 'undefined') {
            //FIXME, figure out how to properly handle "infinite" intervals.
            intervalStart = JulianDate.fromIso8601("0000-01-01T00:00Z");
            intervalStop = JulianDate.fromIso8601("9999-12-31T24:00Z");
        } else {
            iso8601Interval = iso8601Interval.split('/');
            intervalStart = JulianDate.fromIso8601(iso8601Interval[0]);
            intervalStop = JulianDate.fromIso8601(iso8601Interval[1]);
        }

        var existingInterval = this_intervals.findInterval(intervalStart, intervalStop);

        var intervalData;
        if (existingInterval === null) {
            intervalData = {
                interpolationAlgorithm : LinearApproximation,
                numberOfPoints : LinearApproximation.getRequiredDataPoints(1)
            };

            existingInterval = new TimeInterval(intervalStart, intervalStop, true, true);
            existingInterval.data = intervalData;
            this_intervals.addInterval(existingInterval);
        } else {
            intervalData = existingInterval.data;
        }

        var interpolationAlgorithm;
        var interpolationAlgorithmType = czmlInterval.interpolationAlgorithm;
        if (interpolationAlgorithmType) {
            interpolationAlgorithm = interpolators[interpolationAlgorithmType];
            intervalData.interpolationAlgorithm = interpolationAlgorithm;
        }
        var interpolationDegree = czmlInterval.interpolationDegree;
        if (interpolationAlgorithm && interpolationDegree) {
            intervalData.interpolationDegree = interpolationDegree;
            intervalData.numberOfPoints = interpolationAlgorithm.getRequiredDataPoints(interpolationDegree, 0);
            intervalData.xTable = undefined;
            intervalData.yTable = undefined;
        }

        if (this_dataHandler.isSampled(czmlIntervalValue)) {
            if (this.cacheable) {
                intervalData.times = [];
                intervalData.values = [];
                this.cacheable = false;
            }
            var epoch = czmlInterval.epoch;
            if (typeof epoch !== 'undefined') {
                epoch = JulianDate.fromIso8601(epoch);
            }
            DynamicProperty._mergeNewSamples(epoch, intervalData.times, intervalData.values, czmlIntervalValue, this_dataHandler.doublesPerValue, JulianDate.compare);
        } else {
            //Packet itself is a constant value
            intervalData.times = undefined;
            intervalData.values = czmlIntervalValue;
            this.cacheable = true;
        }
    };

    DynamicProperty.prototype.addIntervals = function(czmlIntervals, buffer, sourceUri) {
        if (Array.isArray(czmlIntervals)) {
            for ( var i = 0, len = czmlIntervals.length; i < len; i++) {
                this.addInterval(czmlIntervals[i], buffer, sourceUri);
            }
        } else {
            this.addInterval(czmlIntervals, buffer, sourceUri);
        }
    };

    DynamicProperty.prototype.getValue = function(time) {
        var this_dataHandler = this._dataHandler;
        var interval = this._intervals.findIntervalContainingDate(time);

        if (interval !== null) {
            var intervalData = interval.data;
            var times = intervalData.times;
            var values = intervalData.values;
            if (!this.cacheable && times.length >= 0 && values.length > 0) {
                var index = binarySearch(times, time, JulianDate.compare);

                if (index < 0) {
                    index = ~index;

                    if (index >= times.length) {
                        index = times.length - 1;
                    }

                    var firstIndex = 0;
                    var lastIndex = times.length - 1;

                    var degree = intervalData.numberOfPoints - 1;
                    var pointsInCollection = lastIndex - firstIndex + 1;

                    if (pointsInCollection < degree + 1) {
                        // Use the entire range.
                    } else {
                        var computedFirstIndex = index - ((degree / 2) | 0) - 1;
                        if (computedFirstIndex < firstIndex) {
                            computedFirstIndex = firstIndex;
                        }
                        var computedLastIndex = computedFirstIndex + degree;
                        if (computedLastIndex > lastIndex) {
                            computedLastIndex = lastIndex;
                            computedFirstIndex = computedLastIndex - degree;
                            if (computedFirstIndex < firstIndex) {
                                computedFirstIndex = firstIndex;
                            }
                        }

                        firstIndex = computedFirstIndex;
                        lastIndex = computedLastIndex;
                    }

                    var length = lastIndex - firstIndex + 1;

                    var doublesPerInterpolationValue = this_dataHandler.doublesPerInterpolationValue, xTable = intervalData.xTable, yTable = intervalData.yTable;

                    if (typeof xTable === 'undefined') {
                        xTable = intervalData.xTable = new Array(intervalData.numberOfPoints);
                        yTable = intervalData.yTable = new Array(intervalData.numberOfPoints * doublesPerInterpolationValue);
                    }

                    // Build the tables
                    for ( var i = 0; i < length; ++i) {
                        xTable[i] = times[lastIndex].getSecondsDifference(times[firstIndex + i]);
                    }
                    this_dataHandler.packValuesForInterpolation(values, yTable, firstIndex, lastIndex);

                    // Interpolate!
                    var x = times[lastIndex].getSecondsDifference(time);
                    var interpolationFunction = intervalData.interpolationAlgorithm.interpolateOrderZero;
                    var result = interpolationFunction(x, xTable, yTable, doublesPerInterpolationValue);

                    return this_dataHandler.createValueFromInterpolationResult(result, values, firstIndex, lastIndex);
                }
                return this_dataHandler.createValueFromArray(intervalData.values, index * this_dataHandler.doublesPerValue);
            }
            return this_dataHandler.createValue(intervalData.values);
        }
        return undefined;
    };

    return DynamicProperty;
});<|endoftext|>


<commit_before>/*global define*/
define([
        '../Core/binarySearch',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/JulianDate',
        '../Core/LinearApproximation'
       ], function(
        binarySearch,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        JulianDate,
        LinearApproximation) {
    "use strict";

    var PackableNumber = {
        packedLength : 1,
        pack : function(value, array, startingIndex) {
            startingIndex = defaultValue(startingIndex, 0);
            array[startingIndex] = value;
        },
        unpack : function(array, startingIndex, result) {
            startingIndex = defaultValue(startingIndex, 0);
            return array[startingIndex];
        }
    };

    //We can't use splice for inserting new elements because function apply can't handle
    //a huge number of arguments.  See https://code.google.com/p/chromium/issues/detail?id=56588
    function arrayInsert(array, startIndex, items) {
        var i;
        var arrayLength = array.length;
        var itemsLength = items.length;
        var newLength = arrayLength + itemsLength;

        array.length = newLength;
        if (arrayLength !== startIndex) {
            var q = arrayLength - 1;
            for (i = newLength - 1; i >= startIndex; i--) {
                array[i] = array[q--];
            }
        }

        for (i = 0; i < itemsLength; i++) {
            array[startIndex++] = items[i];
        }
    }

    function convertDate(date, epoch) {
        if (date instanceof JulianDate) {
            return date;
        }
        if (typeof date === 'string') {
            return JulianDate.fromIso8601(date);
        }
        return epoch.addSeconds(date);
    }

    var mergeNewSamples = function(epoch, times, values, newData, packedLength) {
        var newDataIndex = 0;
        var i;
        var prevItem;
        var timesInsertionPoint;
        var valuesInsertionPoint;
        var timesSpliceArgs;
        var valuesSpliceArgs;
        var currentTime;
        var nextTime;

        while (newDataIndex < newData.length) {
            currentTime = convertDate(newData[newDataIndex], epoch);
            timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);

            if (timesInsertionPoint < 0) {
                //Doesn't exist, insert as many additional values as we can.
                timesInsertionPoint = ~timesInsertionPoint;
                timesSpliceArgs = [];

                valuesInsertionPoint = timesInsertionPoint * packedLength;
                valuesSpliceArgs = [];
                prevItem = undefined;
                nextTime = times[timesInsertionPoint];
                while (newDataIndex < newData.length) {
                    currentTime = convertDate(newData[newDataIndex], epoch);
                    if ((defined(prevItem) && JulianDate.compare(prevItem, currentTime) >= 0) || (defined(nextTime) && JulianDate.compare(currentTime, nextTime) >= 0)) {
                        break;
                    }
                    timesSpliceArgs.push(currentTime);
                    newDataIndex = newDataIndex + 1;
                    for (i = 0; i < packedLength; i++) {
                        valuesSpliceArgs.push(newData[newDataIndex]);
                        newDataIndex = newDataIndex + 1;
                    }
                    prevItem = currentTime;
                }

                arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs);
                arrayInsert(times, timesInsertionPoint, timesSpliceArgs);
            } else {
                //Found an exact match
                for (i = 0; i < packedLength; i++) {
                    newDataIndex++;
                    values[(timesInsertionPoint * packedLength) + i] = newData[newDataIndex];
                }
                newDataIndex++;
            }
        }
    };

    /**
     * A {@link Property} whose value is interpolated for a given time from the
     * provided set of samples and specified interpolation algorithm and degree.
     * @alias SampledProperty
     * @constructor
     *
     * @param {Object} type The type of property, which must be Number, String or implement {@link Packable}.
     *
     * @exception {DeveloperError} type is required.
     *
     * @see SampledPositionProperty
     *
     * @example
     * //Create a linearly interpolated Cartesian2
     * var property = new SampledProperty(Cartesian2);
     * property.interpolationDegree = 1;
     * property.interpolationAlgorithm = LinearApproximation;
     *
     * //Populate it with data
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), new Cartesian2(0, 0));
     * property.addSample(JulianDate.fromIso8601(`2012-08-02T00:00:00.00Z`), new Cartesian2(4, 7));
     *
     * //Retrieve an interpolated value
     * var result = property.getValue(JulianDate.fromIso8601(`2012-08-01T12:00:00.00Z`));
     *
     * @example
     * //Create a simple numeric SampledProperty that uses third degree Hermite Polynomial Approximation
     * var property = new SampledProperty(Number);
     * property.interpolationDegree = 3;
     * property.interpolationAlgorithm = HermitePolynomialApproximation;
     *
     * //Populate it with data
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), 1.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:01:00.00Z`), 6.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:02:00.00Z`), 12.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:03:30.00Z`), 5.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:06:30.00Z`), 2.0);
     *
     * //Samples can be added in any order.
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:00:30.00Z`), 6.2);
     *
     * //Retrieve an interpolated value
     * var result = property.getValue(JulianDate.fromIso8601(`2012-08-01T00:02:34.00Z`));
     */
    var SampledProperty = function(type) {
        if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }

        var innerType = type;
        if (innerType === Number) {
            innerType = PackableNumber;
        }

        var packedInterpolationLength = defaultValue(innerType.packedInterpolationLength, innerType.packedLength);

        this._type = type;
        this._innerType = innerType;
        this._interpolationDegree = 1;
        this._interpolationAlgorithm = LinearApproximation;
        this._numberOfPoints = 0;
        this._times = [];
        this._values = [];
        this._xTable = [];
        this._yTable = [];
        this._packedInterpolationLength = packedInterpolationLength;
        this._updateTables = true;
        this._interpolationResult = new Array(packedInterpolationLength);
    };

    defineProperties(SampledProperty.prototype, {
        /**
         * Gets the type of property.
         * @memberof SampledProperty.prototype
         * @type {Object}
         */
        type : {
            get : function() {
                return this._type;
            }
        },
        /**
         * Gets or sets the degree of interpolation to perform when retrieving a value.
         * @memberof SampledProperty.prototype
         * @type {Object}
         * @default 1
         */
        interpolationDegree : {
            get : function() {
                return this._interpolationDegree;
            },
            set : function(value) {
                this._interpolationDegree = value;
                this._updateTables = true;
            }
        },
        /**
         * Gets or sets the interpolation algorithm to use when retrieving a value.
         * @memberof SampledProperty.prototype
         * @type {InterpolationAlgorithm}
         * @default LinearApproximation
         */
        interpolationAlgorithm : {
            get : function() {
                return this._interpolationAlgorithm;
            },
            set : function(value) {
                this._interpolationAlgorithm = value;
                this._updateTables = true;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time.
     * @memberof SampledProperty
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     *
     * @exception {DeveloperError} time is required.
     */
    SampledProperty.prototype.getValue = function(time, result) {
        if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        var innerType = this._innerType;
        var times = this._times;
        var values = this._values;
        var index = binarySearch(times, time, JulianDate.compare);
        if (index < 0) {
            var xTable = this._xTable;
            var yTable = this._yTable;
            var interpolationAlgorithm = this._interpolationAlgorithm;
            var packedInterpolationLength = this._packedInterpolationLength;

            if (this._updateTables) {
                this._updateTables = false;
                var numberOfPoints = Math.min(interpolationAlgorithm.getRequiredDataPoints(this._interpolationDegree), times.length);
                if (numberOfPoints !== this._numberOfPoints) {
                    this._numberOfPoints = numberOfPoints;
                    xTable.length = numberOfPoints;
                    yTable.length = numberOfPoints * packedInterpolationLength;
                }
            }

            var degree = this._numberOfPoints - 1;
            if (degree < 1) {
                return undefined;
            }
            index = ~index;

            if (index >= times.length) {
                index = times.length - 1;
            }

            var firstIndex = 0;
            var lastIndex = times.length - 1;
            var pointsInCollection = lastIndex - firstIndex + 1;

            if (pointsInCollection < degree + 1) {
                // Use the entire range.
            } else {
                var computedFirstIndex = index - ((degree / 2) | 0) - 1;
                if (computedFirstIndex < firstIndex) {
                    computedFirstIndex = firstIndex;
                }
                var computedLastIndex = computedFirstIndex + degree;
                if (computedLastIndex > lastIndex) {
                    computedLastIndex = lastIndex;
                    computedFirstIndex = computedLastIndex - degree;
                    if (computedFirstIndex < firstIndex) {
                        computedFirstIndex = firstIndex;
                    }
                }

                firstIndex = computedFirstIndex;
                lastIndex = computedLastIndex;
            }
            var length = lastIndex - firstIndex + 1;

            // Build the tables
            for ( var i = 0; i < length; ++i) {
                xTable[i] = times[lastIndex].getSecondsDifference(times[firstIndex + i]);
            }

            if (!defined(innerType.convertPackedArrayForInterpolation)) {
                var destinationIndex = 0;
                var packedLength = innerType.packedLength;
                var sourceIndex = firstIndex * packedLength;
                var stop = (lastIndex + 1) * packedLength;

                while (sourceIndex < stop) {
                    yTable[destinationIndex] = values[sourceIndex];
                    sourceIndex++;
                    destinationIndex++;
                }
            } else {
                innerType.convertPackedArrayForInterpolation(values, firstIndex, lastIndex, yTable);
            }

            // Interpolate!
            var x = times[lastIndex].getSecondsDifference(time);
            var interpolationResult = interpolationAlgorithm.interpolateOrderZero(x, xTable, yTable, packedInterpolationLength, this._interpolationResult);

            if (!defined(innerType.unpackInterpolationResult)) {
                return innerType.unpack(interpolationResult, 0, result);
            }
            return innerType.unpackInterpolationResult(interpolationResult, values, firstIndex, lastIndex, result);
        }
        return innerType.unpack(this._values, index * innerType.packedLength, result);
    };

    /**
     * Adds a new sample
     * @memberof SampledProperty
     *
     * @param {JulianDate} time The sample time.
     * @param {Object} value The value at the provided time.
     *
     * @exception {DeveloperError} time is required.
     * @exception {DeveloperError} value is required.
     */
    SampledProperty.prototype.addSample = function(time, value) {
        if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }

        var innerType = this._innerType;
        var data = [time];
        innerType.pack(value, data, 1);
        mergeNewSamples(undefined, this._times, this._values, data, innerType.packedLength);
        this._updateTables = true;
    };

    /**
     * Adds an array of samples
     * @memberof SampledProperty
     *
     * @param {Array} times An array of JulianDate instances where each index is a sample time.
     * @param {Array} values The array of values, where each value corresponds to the provided times index.
     *
     * @exception {DeveloperError} times is required.
     * @exception {DeveloperError} values is required.
     * @exception {DeveloperError} times and values must be the same length..
     */
    SampledProperty.prototype.addSamples = function(times, values) {
        if (!defined(times)) {
            throw new DeveloperError('times is required.');
        }
        if (!defined(values)) {
            throw new DeveloperError('values is required.');
        }
        if (times.length !== values.length) {
            throw new DeveloperError('times and values must be the same length.');
        }

        var innerType = this._innerType;
        var length = times.length;
        var data = [];
        for ( var i = 0; i < length; i++) {
            data.push(times[i]);
            innerType.pack(values[i], data, data.length);
        }
        mergeNewSamples(undefined, this._times, this._values, data, innerType.packedLength);
        this._updateTables = true;
    };

    /**
     * Adds samples as a single packed array where each new sample is represented as a date, followed by the packed representation of the corresponding value.
     * @memberof SampledProperty
     *
     * @param {Array} packedSamples The array of packed samples.
     * @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
     *
     * @exception {DeveloperError} packedSamples is required.
     */
    SampledProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) {
        if (!defined(packedSamples)) {
            throw new DeveloperError('packedSamples is required.');
        }
        mergeNewSamples(epoch, this._times, this._values, packedSamples, this._innerType.packedLength);
        this._updateTables = true;
    };

    //Exposed for testing.
    SampledProperty._mergeNewSamples = mergeNewSamples;

    return SampledProperty;
});<commit_msg>Rename `SampledProperty._updateTables` to `SampledProperty._updateTableLength`<commit_after>/*global define*/
define([
        '../Core/binarySearch',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/JulianDate',
        '../Core/LinearApproximation'
       ], function(
        binarySearch,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        JulianDate,
        LinearApproximation) {
    "use strict";

    var PackableNumber = {
        packedLength : 1,
        pack : function(value, array, startingIndex) {
            startingIndex = defaultValue(startingIndex, 0);
            array[startingIndex] = value;
        },
        unpack : function(array, startingIndex, result) {
            startingIndex = defaultValue(startingIndex, 0);
            return array[startingIndex];
        }
    };

    //We can't use splice for inserting new elements because function apply can't handle
    //a huge number of arguments.  See https://code.google.com/p/chromium/issues/detail?id=56588
    function arrayInsert(array, startIndex, items) {
        var i;
        var arrayLength = array.length;
        var itemsLength = items.length;
        var newLength = arrayLength + itemsLength;

        array.length = newLength;
        if (arrayLength !== startIndex) {
            var q = arrayLength - 1;
            for (i = newLength - 1; i >= startIndex; i--) {
                array[i] = array[q--];
            }
        }

        for (i = 0; i < itemsLength; i++) {
            array[startIndex++] = items[i];
        }
    }

    function convertDate(date, epoch) {
        if (date instanceof JulianDate) {
            return date;
        }
        if (typeof date === 'string') {
            return JulianDate.fromIso8601(date);
        }
        return epoch.addSeconds(date);
    }

    var mergeNewSamples = function(epoch, times, values, newData, packedLength) {
        var newDataIndex = 0;
        var i;
        var prevItem;
        var timesInsertionPoint;
        var valuesInsertionPoint;
        var timesSpliceArgs;
        var valuesSpliceArgs;
        var currentTime;
        var nextTime;

        while (newDataIndex < newData.length) {
            currentTime = convertDate(newData[newDataIndex], epoch);
            timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);

            if (timesInsertionPoint < 0) {
                //Doesn't exist, insert as many additional values as we can.
                timesInsertionPoint = ~timesInsertionPoint;
                timesSpliceArgs = [];

                valuesInsertionPoint = timesInsertionPoint * packedLength;
                valuesSpliceArgs = [];
                prevItem = undefined;
                nextTime = times[timesInsertionPoint];
                while (newDataIndex < newData.length) {
                    currentTime = convertDate(newData[newDataIndex], epoch);
                    if ((defined(prevItem) && JulianDate.compare(prevItem, currentTime) >= 0) || (defined(nextTime) && JulianDate.compare(currentTime, nextTime) >= 0)) {
                        break;
                    }
                    timesSpliceArgs.push(currentTime);
                    newDataIndex = newDataIndex + 1;
                    for (i = 0; i < packedLength; i++) {
                        valuesSpliceArgs.push(newData[newDataIndex]);
                        newDataIndex = newDataIndex + 1;
                    }
                    prevItem = currentTime;
                }

                arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs);
                arrayInsert(times, timesInsertionPoint, timesSpliceArgs);
            } else {
                //Found an exact match
                for (i = 0; i < packedLength; i++) {
                    newDataIndex++;
                    values[(timesInsertionPoint * packedLength) + i] = newData[newDataIndex];
                }
                newDataIndex++;
            }
        }
    };

    /**
     * A {@link Property} whose value is interpolated for a given time from the
     * provided set of samples and specified interpolation algorithm and degree.
     * @alias SampledProperty
     * @constructor
     *
     * @param {Object} type The type of property, which must be Number, String or implement {@link Packable}.
     *
     * @exception {DeveloperError} type is required.
     *
     * @see SampledPositionProperty
     *
     * @example
     * //Create a linearly interpolated Cartesian2
     * var property = new SampledProperty(Cartesian2);
     * property.interpolationDegree = 1;
     * property.interpolationAlgorithm = LinearApproximation;
     *
     * //Populate it with data
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), new Cartesian2(0, 0));
     * property.addSample(JulianDate.fromIso8601(`2012-08-02T00:00:00.00Z`), new Cartesian2(4, 7));
     *
     * //Retrieve an interpolated value
     * var result = property.getValue(JulianDate.fromIso8601(`2012-08-01T12:00:00.00Z`));
     *
     * @example
     * //Create a simple numeric SampledProperty that uses third degree Hermite Polynomial Approximation
     * var property = new SampledProperty(Number);
     * property.interpolationDegree = 3;
     * property.interpolationAlgorithm = HermitePolynomialApproximation;
     *
     * //Populate it with data
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), 1.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:01:00.00Z`), 6.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:02:00.00Z`), 12.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:03:30.00Z`), 5.0);
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:06:30.00Z`), 2.0);
     *
     * //Samples can be added in any order.
     * property.addSample(JulianDate.fromIso8601(`2012-08-01T00:00:30.00Z`), 6.2);
     *
     * //Retrieve an interpolated value
     * var result = property.getValue(JulianDate.fromIso8601(`2012-08-01T00:02:34.00Z`));
     */
    var SampledProperty = function(type) {
        if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }

        var innerType = type;
        if (innerType === Number) {
            innerType = PackableNumber;
        }

        var packedInterpolationLength = defaultValue(innerType.packedInterpolationLength, innerType.packedLength);

        this._type = type;
        this._innerType = innerType;
        this._interpolationDegree = 1;
        this._interpolationAlgorithm = LinearApproximation;
        this._numberOfPoints = 0;
        this._times = [];
        this._values = [];
        this._xTable = [];
        this._yTable = [];
        this._packedInterpolationLength = packedInterpolationLength;
        this._updateTableLength = true;
        this._interpolationResult = new Array(packedInterpolationLength);
    };

    defineProperties(SampledProperty.prototype, {
        /**
         * Gets the type of property.
         * @memberof SampledProperty.prototype
         * @type {Object}
         */
        type : {
            get : function() {
                return this._type;
            }
        },
        /**
         * Gets or sets the degree of interpolation to perform when retrieving a value.
         * @memberof SampledProperty.prototype
         * @type {Object}
         * @default 1
         */
        interpolationDegree : {
            get : function() {
                return this._interpolationDegree;
            },
            set : function(value) {
                this._interpolationDegree = value;
                this._updateTableLength = true;
            }
        },
        /**
         * Gets or sets the interpolation algorithm to use when retrieving a value.
         * @memberof SampledProperty.prototype
         * @type {InterpolationAlgorithm}
         * @default LinearApproximation
         */
        interpolationAlgorithm : {
            get : function() {
                return this._interpolationAlgorithm;
            },
            set : function(value) {
                this._interpolationAlgorithm = value;
                this._updateTableLength = true;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time.
     * @memberof SampledProperty
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     *
     * @exception {DeveloperError} time is required.
     */
    SampledProperty.prototype.getValue = function(time, result) {
        if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        var innerType = this._innerType;
        var times = this._times;
        var values = this._values;
        var index = binarySearch(times, time, JulianDate.compare);
        if (index < 0) {
            var xTable = this._xTable;
            var yTable = this._yTable;
            var interpolationAlgorithm = this._interpolationAlgorithm;
            var packedInterpolationLength = this._packedInterpolationLength;

            if (this._updateTableLength) {
                this._updateTableLength = false;
                var numberOfPoints = Math.min(interpolationAlgorithm.getRequiredDataPoints(this._interpolationDegree), times.length);
                if (numberOfPoints !== this._numberOfPoints) {
                    this._numberOfPoints = numberOfPoints;
                    xTable.length = numberOfPoints;
                    yTable.length = numberOfPoints * packedInterpolationLength;
                }
            }

            var degree = this._numberOfPoints - 1;
            if (degree < 1) {
                return undefined;
            }
            index = ~index;

            if (index >= times.length) {
                index = times.length - 1;
            }

            var firstIndex = 0;
            var lastIndex = times.length - 1;
            var pointsInCollection = lastIndex - firstIndex + 1;

            if (pointsInCollection < degree + 1) {
                // Use the entire range.
            } else {
                var computedFirstIndex = index - ((degree / 2) | 0) - 1;
                if (computedFirstIndex < firstIndex) {
                    computedFirstIndex = firstIndex;
                }
                var computedLastIndex = computedFirstIndex + degree;
                if (computedLastIndex > lastIndex) {
                    computedLastIndex = lastIndex;
                    computedFirstIndex = computedLastIndex - degree;
                    if (computedFirstIndex < firstIndex) {
                        computedFirstIndex = firstIndex;
                    }
                }

                firstIndex = computedFirstIndex;
                lastIndex = computedLastIndex;
            }
            var length = lastIndex - firstIndex + 1;

            // Build the tables
            for ( var i = 0; i < length; ++i) {
                xTable[i] = times[lastIndex].getSecondsDifference(times[firstIndex + i]);
            }

            if (!defined(innerType.convertPackedArrayForInterpolation)) {
                var destinationIndex = 0;
                var packedLength = innerType.packedLength;
                var sourceIndex = firstIndex * packedLength;
                var stop = (lastIndex + 1) * packedLength;

                while (sourceIndex < stop) {
                    yTable[destinationIndex] = values[sourceIndex];
                    sourceIndex++;
                    destinationIndex++;
                }
            } else {
                innerType.convertPackedArrayForInterpolation(values, firstIndex, lastIndex, yTable);
            }

            // Interpolate!
            var x = times[lastIndex].getSecondsDifference(time);
            var interpolationResult = interpolationAlgorithm.interpolateOrderZero(x, xTable, yTable, packedInterpolationLength, this._interpolationResult);

            if (!defined(innerType.unpackInterpolationResult)) {
                return innerType.unpack(interpolationResult, 0, result);
            }
            return innerType.unpackInterpolationResult(interpolationResult, values, firstIndex, lastIndex, result);
        }
        return innerType.unpack(this._values, index * innerType.packedLength, result);
    };

    /**
     * Adds a new sample
     * @memberof SampledProperty
     *
     * @param {JulianDate} time The sample time.
     * @param {Object} value The value at the provided time.
     *
     * @exception {DeveloperError} time is required.
     * @exception {DeveloperError} value is required.
     */
    SampledProperty.prototype.addSample = function(time, value) {
        if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }

        var innerType = this._innerType;
        var data = [time];
        innerType.pack(value, data, 1);
        mergeNewSamples(undefined, this._times, this._values, data, innerType.packedLength);
        this._updateTableLength = true;
    };

    /**
     * Adds an array of samples
     * @memberof SampledProperty
     *
     * @param {Array} times An array of JulianDate instances where each index is a sample time.
     * @param {Array} values The array of values, where each value corresponds to the provided times index.
     *
     * @exception {DeveloperError} times is required.
     * @exception {DeveloperError} values is required.
     * @exception {DeveloperError} times and values must be the same length..
     */
    SampledProperty.prototype.addSamples = function(times, values) {
        if (!defined(times)) {
            throw new DeveloperError('times is required.');
        }
        if (!defined(values)) {
            throw new DeveloperError('values is required.');
        }
        if (times.length !== values.length) {
            throw new DeveloperError('times and values must be the same length.');
        }

        var innerType = this._innerType;
        var length = times.length;
        var data = [];
        for ( var i = 0; i < length; i++) {
            data.push(times[i]);
            innerType.pack(values[i], data, data.length);
        }
        mergeNewSamples(undefined, this._times, this._values, data, innerType.packedLength);
        this._updateTableLength = true;
    };

    /**
     * Adds samples as a single packed array where each new sample is represented as a date, followed by the packed representation of the corresponding value.
     * @memberof SampledProperty
     *
     * @param {Array} packedSamples The array of packed samples.
     * @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
     *
     * @exception {DeveloperError} packedSamples is required.
     */
    SampledProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) {
        if (!defined(packedSamples)) {
            throw new DeveloperError('packedSamples is required.');
        }
        mergeNewSamples(epoch, this._times, this._values, packedSamples, this._innerType.packedLength);
        this._updateTableLength = true;
    };

    //Exposed for testing.
    SampledProperty._mergeNewSamples = mergeNewSamples;

    return SampledProperty;
});<|endoftext|>


<commit_before>import { createElement as h, Fragment } from 'react'

import useDurations from '../../api/hooks/useDurations'
import enhanceDurations from '../../enhancers/enhanceDurations'

import CardWidget from '../cards/CardWidget'
import RendererDurations from '../renderers/RendererDurations'

const RouteDurations = (props) => {

	const durations = useDurations(props.filter.interval)

	return (
		h(Fragment, {},
			h(CardWidget, {
				key: durations.value.statistics.id,
				wide: true,
				headline: 'Durations',
				widget: {
					Renderer: RendererDurations,
					variables: {
						interval: props.filter.interval
					},
					value: enhanceDurations(durations.value.statistics.durations, 14),
					fetching: durations.fetching
				}
			}),
			durations.value.domains.map((domain) => {
				return h(CardWidget, {
					headline: domain.title,
					onMore: () => props.setRoute(`/domains/${ domain.id }`),
					widget: {
						Renderer: RendererDurations,
						variables: {
							interval: props.filter.interval
						},
						value: enhanceDurations(domain.statistics.durations, 7),
						fetching: durations.fetching
					}
				})
			})
		)
	)

}

export default RouteDurations<commit_msg>Add missing key<commit_after>import { createElement as h, Fragment } from 'react'

import useDurations from '../../api/hooks/useDurations'
import enhanceDurations from '../../enhancers/enhanceDurations'

import CardWidget from '../cards/CardWidget'
import RendererDurations from '../renderers/RendererDurations'

const RouteDurations = (props) => {

	const durations = useDurations(props.filter.interval)

	return (
		h(Fragment, {},
			h(CardWidget, {
				key: durations.value.statistics.id,
				wide: true,
				headline: 'Durations',
				widget: {
					Renderer: RendererDurations,
					variables: {
						interval: props.filter.interval
					},
					value: enhanceDurations(durations.value.statistics.durations, 14),
					fetching: durations.fetching
				}
			}),
			durations.value.domains.map((domain) => {
				return h(CardWidget, {
					key: domain.statistics.id,
					headline: domain.title,
					onMore: () => props.setRoute(`/domains/${ domain.id }`),
					widget: {
						Renderer: RendererDurations,
						variables: {
							interval: props.filter.interval
						},
						value: enhanceDurations(domain.statistics.durations, 7),
						fetching: durations.fetching
					}
				})
			})
		)
	)

}

export default RouteDurations<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { EntityDataModelApi } from 'loom-data';
import Utils from '../../../../utils/Utils';
import AuthService from '../../../../utils/AuthService';
import { EntitySet } from './EntitySet';
import styles from '../styles.module.css';

export class EntitySetList extends React.Component {
  static propTypes = {
    auth: PropTypes.instanceOf(AuthService)
  }

  constructor(props, context) {
    super(props, context);
    this.state = { entitySets: [] };
  }

  componentDidMount() {
    EntityDataModelApi.getAllEntitySets()
      .then((entitySets) => {
        this.setState({ entitySets: Utils.addKeysToArray(entitySets) });
      });
  }

  render() {
    const entitySetList = this.state.entitySets.map((entitySet) => {
      return (<EntitySet
        key={entitySet.key}
        name={entitySet.entitySet.name}
        title={entitySet.entitySet.title}
        type={entitySet.entitySet.type}
        permissions={entitySet.permissions}
        isOwner={entitySet.isOwner}
        auth={this.props.auth}
      />);
    });
    return (

        {entitySetList}

    );
  }
}

export default EntitySetList;
<commit_msg>error handling for entity set loading<commit_after>import React, { PropTypes } from 'react';
import { EntityDataModelApi } from 'loom-data';
import Utils from '../../../../utils/Utils';
import AuthService from '../../../../utils/AuthService';
import { EntitySet } from './EntitySet';
import styles from '../styles.module.css';

export class EntitySetList extends React.Component {
  static propTypes = {
    auth: PropTypes.instanceOf(AuthService)
  }

  constructor(props, context) {
    super(props, context);
    this.state = {
      entitySets: [],
      error: false
    };
  }

  errorClass = {
    true: styles.error,
    false: styles.hidden
  }

  componentDidMount() {
    EntityDataModelApi.getAllEntitySets()
      .then((entitySets) => {
        this.setState({ entitySets: Utils.addKeysToArray(entitySets) });
      }).catch(() => {
        this.setState({ error: true });
      });
  }

  render() {
    const entitySetList = this.state.entitySets.map((entitySet) => {
      return (<EntitySet
        key={entitySet.key}
        name={entitySet.entitySet.name}
        title={entitySet.entitySet.title}
        type={entitySet.entitySet.type}
        permissions={entitySet.permissions}
        isOwner={entitySet.isOwner}
        auth={this.props.auth}
      />);
    });
    return (

Unable to load entity sets.

        {entitySetList}

    );
  }
}

export default EntitySetList;
<|endoftext|>


<commit_before>var BaseElement = require("../BaseElement");
var CenteringTextElement = require("../../util/CenteringTextElement");
var drawTools = require("../drawTools")();
var forceLayoutNodeFunctions = require("../forceLayoutNodeFunctions")();
var rectangularElementTools = require("../rectangularElementTools")();

module.exports = (function () {

	// Static variables
	var labelHeight = 28,
		labelWidth = 80,
		smallestRadius = labelHeight / 2;

	// Constructor, private variables and privileged methods
	var Base = function (graph) {
		BaseElement.apply(this, arguments);

		var that = this,
		// Basic attributes
			cardinality,
			domain,
			inverse,
			link,
			minCardinality,
			maxCardinality,
			range,
			subproperties,
			superproperties,
		// Style attributes
			linkType = "normal",
			markerType = "filled",
			labelVisible = true,
		// Element containers
			cardinalityElement,
			labelElement,
			linkGroup,
			markerElement,
		// Other
			pinGroupElement,
			redundantProperties = [];

		// Properties
		this.cardinality = function (p) {
			if (!arguments.length) return cardinality;
			cardinality = p;
			return this;
		};

		this.cardinalityElement = function (p) {
			if (!arguments.length) return cardinalityElement;
			cardinalityElement = p;
			return this;
		};

		this.domain = function (p) {
			if (!arguments.length) return domain;
			domain = p;
			return this;
		};

		this.inverse = function (p) {
			if (!arguments.length) return inverse;
			inverse = p;
			return this;
		};

		this.labelElement = function (p) {
			if (!arguments.length) return labelElement;
			labelElement = p;
			return this;
		};

		this.labelVisible = function (p) {
			if (!arguments.length) return labelVisible;
			labelVisible = p;
			return this;
		};

		this.link = function (p) {
			if (!arguments.length) return link;
			link = p;
			return this;
		};

		this.linkGroup = function (p) {
			if (!arguments.length) return linkGroup;
			linkGroup = p;
			return this;
		};

		this.linkType = function (p) {
			if (!arguments.length) return linkType;
			linkType = p;
			return this;
		};

		this.markerElement = function (p) {
			if (!arguments.length) return markerElement;
			markerElement = p;
			return this;
		};

		this.markerType = function (p) {
			if (!arguments.length) return markerType;
			markerType = p;
			return this;
		};

		this.maxCardinality = function (p) {
			if (!arguments.length) return maxCardinality;
			maxCardinality = p;
			return this;
		};

		this.minCardinality = function (p) {
			if (!arguments.length) return minCardinality;
			minCardinality = p;
			return this;
		};

		this.range = function (p) {
			if (!arguments.length) return range;
			range = p;
			return this;
		};

		this.redundantProperties = function (p) {
			if (!arguments.length) return redundantProperties;
			redundantProperties = p;
			return this;
		};

		this.subproperties = function (p) {
			if (!arguments.length) return subproperties;
			subproperties = p;
			return this;
		};

		this.superproperties = function (p) {
			if (!arguments.length) return superproperties;
			superproperties = p;
			return this;
		};

		// Functions
		this.distanceToBorder = function (dx, dy) {
			return rectangularElementTools.distanceToBorder(that, dx, dy);
		};

		this.linkHasMarker = function () {
			return linkType !== "dashed";
		};

		this.markerId = function () {
			return "marker" + that.id();
		};

		this.toggleFocus = function () {
			that.focused(!that.focused());
			labelElement.select("rect").classed("focused", that.focused());
		};

		// Reused functions TODO refactor
		this.draw = function (labelGroup) {
			function attachLabel(property) {
				// Draw the label and its background
				var label = labelGroup.append("g")
					.datum(property)
					.classed("label", true)
					.attr("id", property.id());
				property.addRect(label);

				// Attach the text and perhaps special elements
				var textBox = new CenteringTextElement(label);
				if (property instanceof require("./implementations/OwlDisjointWith")) {
					property.addDisjointLabel(labelGroup, textBox);
					return label;
				} else {
					textBox.addText(property.labelForCurrentLanguage());
				}

				textBox.addSubText(property.indicationString());
				property.addEquivalentsToLabel(textBox);

				return label;
			}

			if (!that.labelVisible()) {
				return undefined;
			}

			that.labelElement(attachLabel(that));

			// Draw an inverse label and reposition both labels if necessary
			if (that.inverse()) {
				var yTransformation = (that.height() / 2) + 1 /* additional space */;
				that.inverse()
					.labelElement(attachLabel(that.inverse()));

				that.labelElement()
					.attr("transform", "translate(" + 0 + ",-" + yTransformation + ")");
				that.inverse()
					.labelElement()
					.attr("transform", "translate(" + 0 + "," + yTransformation + ")");
			}

			return that.labelElement();
		};

		this.addRect = function (groupTag) {
			var rect = groupTag.append("rect")
				.classed(that.styleClass(), true)
				.classed("property", true)
				.attr("x", -that.width() / 2)
				.attr("y", -that.height() / 2)
				.attr("width", that.width())
				.attr("height", that.height())
				.on("mouseover", function () {
					onMouseOver();
				})
				.on("mouseout", function () {
					onMouseOut();
				});

			rect.append("title")
				.text(that.labelForCurrentLanguage());

			if (that.visualAttributes()) {
				rect.classed(that.visualAttributes(), true);
			}
		};
		this.addDisjointLabel = function (groupTag, textTag) {
			groupTag.append("circle")
				.classed("symbol", true)
				.classed("fineline", true)
				.classed("embedded", true)
				.attr("cx", -12.5)
				.attr("r", 10);

			groupTag.append("circle")
				.classed("symbol", true)
				.classed("fineline", true)
				.classed("embedded", true)
				.attr("cx", 12.5)
				.attr("r", 10);

			if (!graph.options().compactNotation()) {
				textTag.addSubText("disjoint");
			}
			textTag.translation(0, 20);
		};
		this.addEquivalentsToLabel = function (textBox) {
			if (that.equivalents()) {
				var equivalentLabels,
					equivalentString;

				equivalentLabels = that.equivalents().map(function (property) {
					return property.labelForCurrentLanguage();
				});
				equivalentString = equivalentLabels.join(", ");

				textBox.addEquivalents(equivalentString);
			}
		};
		this.drawCardinality = function (cardinalityGroup) {
			if (that.minCardinality() === undefined &&
				that.maxCardinality() === undefined &&
				that.cardinality() === undefined) {
				return undefined;
			}

			// Drawing cardinality groups
			that.cardinalityElement(cardinalityGroup.classed("cardinality", true));

			var cardText = cardinalityGroup.append("text")
				.classed("cardinality", true)
				.attr("text-anchor", "middle")
				.attr("dy", "0.5ex");

			if (that.minCardinality() !== undefined) {
				var cardString = that.minCardinality() + "..";
				cardString += that.maxCardinality() !== undefined ? that.maxCardinality() : "*";

				cardText.text(cardString);
			} else if (that.maxCardinality() !== undefined) {
				cardText.text("*.." + that.maxCardinality());
			} else if (that.cardinality() !== undefined) {
				cardText.text(that.cardinality());
			}

			return that.cardinalityElement();
		};

		that.setHighlighting = function (enable) {
			if (that.labelElement()) {
				that.labelElement().select("rect").classed("hovered", enable);
			}
			that.linkGroup().selectAll("path, text").classed("hovered", enable);
			that.markerElement().select("path").classed("hovered", enable);
			if (that.cardinalityElement()) {
				that.cardinalityElement().classed("hovered", enable);
			}

			var subAndSuperProperties = getSubAndSuperProperties();
			subAndSuperProperties.forEach(function (property) {
				property.labelElement().select("rect")
					.classed("indirectHighlighting", enable);
			});
		};

		/**
		 * Combines the sub- and superproperties into a single array, because
		 * they're often used equivalently.
		 * @returns {Array}
		 */
		function getSubAndSuperProperties() {
			var properties = [];

			if (that.subproperties()) {
				properties = properties.concat(that.subproperties());
			}
			if (that.superproperties()) {
				properties = properties.concat(that.superproperties());
			}

			return properties;
		}

		/**
		 * Foregrounds the property, its inverse and the link.
		 */
		this.foreground = function () {
			var selectedLabelGroup = that.labelElement().node().parentNode,
				labelContainer = selectedLabelGroup.parentNode,
				selectedLinkGroup = that.linkGroup().node(),
				linkContainer = that.linkGroup().node().parentNode;

			// Append hovered element as last child to the container list.
			labelContainer.appendChild(selectedLabelGroup);
			linkContainer.appendChild(selectedLinkGroup);
		};

		/**
		 * Foregrounds the sub- and superproperties of this property.
		 * This is separated from the foreground-function to prevent endless loops.
		 */
		function foregroundSubAndSuperProperties() {
			var subAndSuperProperties = getSubAndSuperProperties();

			subAndSuperProperties.forEach(function (property) {
				property.foreground();
			});
		}

		function onMouseOver() {
			if (that.mouseEntered()) {
				return;
			}
			that.mouseEntered(true);
			that.setHighlighting(true);

			that.foreground();
			foregroundSubAndSuperProperties();
		}

		function onMouseOut() {
			that.mouseEntered(false);
			that.setHighlighting(false);
		}

		this.drawPin = function () {
			that.pinned(true);
			pinGroupElement = drawTools.drawPin(that.labelElement(), 20, -20, this.removePin);
		};

		/**
		 * Removes the pin and refreshs the graph to update the force layout.
		 */
		this.removePin = function () {
			that.pinned(false);
			if (pinGroupElement) {
				pinGroupElement.remove();
			}
			graph.updateStyle();
		};

		forceLayoutNodeFunctions.addTo(this);
	};

	Base.prototype = Object.create(BaseElement.prototype);
	Base.prototype.constructor = Base;

	Base.prototype.height = function () {
		return labelHeight;
	};

	Base.prototype.width = function () {
		return labelWidth;
	};

	Base.prototype.actualRadius = function () {
		return smallestRadius;
	};

	Base.prototype.textWidth = Base.prototype.width;

	return Base;
}());
<commit_msg>Adjust position of property pin<commit_after>var BaseElement = require("../BaseElement");
var CenteringTextElement = require("../../util/CenteringTextElement");
var drawTools = require("../drawTools")();
var forceLayoutNodeFunctions = require("../forceLayoutNodeFunctions")();
var rectangularElementTools = require("../rectangularElementTools")();

module.exports = (function () {

	// Static variables
	var labelHeight = 28,
		labelWidth = 80,
		smallestRadius = labelHeight / 2;

	// Constructor, private variables and privileged methods
	var Base = function (graph) {
		BaseElement.apply(this, arguments);

		var that = this,
		// Basic attributes
			cardinality,
			domain,
			inverse,
			link,
			minCardinality,
			maxCardinality,
			range,
			subproperties,
			superproperties,
		// Style attributes
			linkType = "normal",
			markerType = "filled",
			labelVisible = true,
		// Element containers
			cardinalityElement,
			labelElement,
			linkGroup,
			markerElement,
		// Other
			pinGroupElement,
			redundantProperties = [];

		// Properties
		this.cardinality = function (p) {
			if (!arguments.length) return cardinality;
			cardinality = p;
			return this;
		};

		this.cardinalityElement = function (p) {
			if (!arguments.length) return cardinalityElement;
			cardinalityElement = p;
			return this;
		};

		this.domain = function (p) {
			if (!arguments.length) return domain;
			domain = p;
			return this;
		};

		this.inverse = function (p) {
			if (!arguments.length) return inverse;
			inverse = p;
			return this;
		};

		this.labelElement = function (p) {
			if (!arguments.length) return labelElement;
			labelElement = p;
			return this;
		};

		this.labelVisible = function (p) {
			if (!arguments.length) return labelVisible;
			labelVisible = p;
			return this;
		};

		this.link = function (p) {
			if (!arguments.length) return link;
			link = p;
			return this;
		};

		this.linkGroup = function (p) {
			if (!arguments.length) return linkGroup;
			linkGroup = p;
			return this;
		};

		this.linkType = function (p) {
			if (!arguments.length) return linkType;
			linkType = p;
			return this;
		};

		this.markerElement = function (p) {
			if (!arguments.length) return markerElement;
			markerElement = p;
			return this;
		};

		this.markerType = function (p) {
			if (!arguments.length) return markerType;
			markerType = p;
			return this;
		};

		this.maxCardinality = function (p) {
			if (!arguments.length) return maxCardinality;
			maxCardinality = p;
			return this;
		};

		this.minCardinality = function (p) {
			if (!arguments.length) return minCardinality;
			minCardinality = p;
			return this;
		};

		this.range = function (p) {
			if (!arguments.length) return range;
			range = p;
			return this;
		};

		this.redundantProperties = function (p) {
			if (!arguments.length) return redundantProperties;
			redundantProperties = p;
			return this;
		};

		this.subproperties = function (p) {
			if (!arguments.length) return subproperties;
			subproperties = p;
			return this;
		};

		this.superproperties = function (p) {
			if (!arguments.length) return superproperties;
			superproperties = p;
			return this;
		};

		// Functions
		this.distanceToBorder = function (dx, dy) {
			return rectangularElementTools.distanceToBorder(that, dx, dy);
		};

		this.linkHasMarker = function () {
			return linkType !== "dashed";
		};

		this.markerId = function () {
			return "marker" + that.id();
		};

		this.toggleFocus = function () {
			that.focused(!that.focused());
			labelElement.select("rect").classed("focused", that.focused());
		};

		// Reused functions TODO refactor
		this.draw = function (labelGroup) {
			function attachLabel(property) {
				// Draw the label and its background
				var label = labelGroup.append("g")
					.datum(property)
					.classed("label", true)
					.attr("id", property.id());
				property.addRect(label);

				// Attach the text and perhaps special elements
				var textBox = new CenteringTextElement(label);
				if (property instanceof require("./implementations/OwlDisjointWith")) {
					property.addDisjointLabel(labelGroup, textBox);
					return label;
				} else {
					textBox.addText(property.labelForCurrentLanguage());
				}

				textBox.addSubText(property.indicationString());
				property.addEquivalentsToLabel(textBox);

				return label;
			}

			if (!that.labelVisible()) {
				return undefined;
			}

			that.labelElement(attachLabel(that));

			// Draw an inverse label and reposition both labels if necessary
			if (that.inverse()) {
				var yTransformation = (that.height() / 2) + 1 /* additional space */;
				that.inverse()
					.labelElement(attachLabel(that.inverse()));

				that.labelElement()
					.attr("transform", "translate(" + 0 + ",-" + yTransformation + ")");
				that.inverse()
					.labelElement()
					.attr("transform", "translate(" + 0 + "," + yTransformation + ")");
			}

			return that.labelElement();
		};

		this.addRect = function (groupTag) {
			var rect = groupTag.append("rect")
				.classed(that.styleClass(), true)
				.classed("property", true)
				.attr("x", -that.width() / 2)
				.attr("y", -that.height() / 2)
				.attr("width", that.width())
				.attr("height", that.height())
				.on("mouseover", function () {
					onMouseOver();
				})
				.on("mouseout", function () {
					onMouseOut();
				});

			rect.append("title")
				.text(that.labelForCurrentLanguage());

			if (that.visualAttributes()) {
				rect.classed(that.visualAttributes(), true);
			}
		};
		this.addDisjointLabel = function (groupTag, textTag) {
			groupTag.append("circle")
				.classed("symbol", true)
				.classed("fineline", true)
				.classed("embedded", true)
				.attr("cx", -12.5)
				.attr("r", 10);

			groupTag.append("circle")
				.classed("symbol", true)
				.classed("fineline", true)
				.classed("embedded", true)
				.attr("cx", 12.5)
				.attr("r", 10);

			if (!graph.options().compactNotation()) {
				textTag.addSubText("disjoint");
			}
			textTag.translation(0, 20);
		};
		this.addEquivalentsToLabel = function (textBox) {
			if (that.equivalents()) {
				var equivalentLabels,
					equivalentString;

				equivalentLabels = that.equivalents().map(function (property) {
					return property.labelForCurrentLanguage();
				});
				equivalentString = equivalentLabels.join(", ");

				textBox.addEquivalents(equivalentString);
			}
		};
		this.drawCardinality = function (cardinalityGroup) {
			if (that.minCardinality() === undefined &&
				that.maxCardinality() === undefined &&
				that.cardinality() === undefined) {
				return undefined;
			}

			// Drawing cardinality groups
			that.cardinalityElement(cardinalityGroup.classed("cardinality", true));

			var cardText = cardinalityGroup.append("text")
				.classed("cardinality", true)
				.attr("text-anchor", "middle")
				.attr("dy", "0.5ex");

			if (that.minCardinality() !== undefined) {
				var cardString = that.minCardinality() + "..";
				cardString += that.maxCardinality() !== undefined ? that.maxCardinality() : "*";

				cardText.text(cardString);
			} else if (that.maxCardinality() !== undefined) {
				cardText.text("*.." + that.maxCardinality());
			} else if (that.cardinality() !== undefined) {
				cardText.text(that.cardinality());
			}

			return that.cardinalityElement();
		};

		that.setHighlighting = function (enable) {
			if (that.labelElement()) {
				that.labelElement().select("rect").classed("hovered", enable);
			}
			that.linkGroup().selectAll("path, text").classed("hovered", enable);
			that.markerElement().select("path").classed("hovered", enable);
			if (that.cardinalityElement()) {
				that.cardinalityElement().classed("hovered", enable);
			}

			var subAndSuperProperties = getSubAndSuperProperties();
			subAndSuperProperties.forEach(function (property) {
				property.labelElement().select("rect")
					.classed("indirectHighlighting", enable);
			});
		};

		/**
		 * Combines the sub- and superproperties into a single array, because
		 * they're often used equivalently.
		 * @returns {Array}
		 */
		function getSubAndSuperProperties() {
			var properties = [];

			if (that.subproperties()) {
				properties = properties.concat(that.subproperties());
			}
			if (that.superproperties()) {
				properties = properties.concat(that.superproperties());
			}

			return properties;
		}

		/**
		 * Foregrounds the property, its inverse and the link.
		 */
		this.foreground = function () {
			var selectedLabelGroup = that.labelElement().node().parentNode,
				labelContainer = selectedLabelGroup.parentNode,
				selectedLinkGroup = that.linkGroup().node(),
				linkContainer = that.linkGroup().node().parentNode;

			// Append hovered element as last child to the container list.
			labelContainer.appendChild(selectedLabelGroup);
			linkContainer.appendChild(selectedLinkGroup);
		};

		/**
		 * Foregrounds the sub- and superproperties of this property.
		 * This is separated from the foreground-function to prevent endless loops.
		 */
		function foregroundSubAndSuperProperties() {
			var subAndSuperProperties = getSubAndSuperProperties();

			subAndSuperProperties.forEach(function (property) {
				property.foreground();
			});
		}

		function onMouseOver() {
			if (that.mouseEntered()) {
				return;
			}
			that.mouseEntered(true);
			that.setHighlighting(true);

			that.foreground();
			foregroundSubAndSuperProperties();
		}

		function onMouseOut() {
			that.mouseEntered(false);
			that.setHighlighting(false);
		}

		this.drawPin = function () {
			that.pinned(true);
			pinGroupElement = drawTools.drawPin(that.labelElement(), 20, -25, this.removePin);
		};

		/**
		 * Removes the pin and refreshs the graph to update the force layout.
		 */
		this.removePin = function () {
			that.pinned(false);
			if (pinGroupElement) {
				pinGroupElement.remove();
			}
			graph.updateStyle();
		};

		forceLayoutNodeFunctions.addTo(this);
	};

	Base.prototype = Object.create(BaseElement.prototype);
	Base.prototype.constructor = Base;

	Base.prototype.height = function () {
		return labelHeight;
	};

	Base.prototype.width = function () {
		return labelWidth;
	};

	Base.prototype.actualRadius = function () {
		return smallestRadius;
	};

	Base.prototype.textWidth = Base.prototype.width;

	return Base;
}());
<|endoftext|>