<commit_before>import { Store }                from 'airflux';
import Firebase                 from 'firebase';

import * as ConfigActions       from '../config/ConfigActions';
import * as NotificationActions from './NotificationActions';

class AuthStore extends Store {

    state = {};

    constructor() {
        super();
        ConfigActions.loadConfig.listen( this.onConfigLoaded.bind( this ) );
    }

    get currentUser() { return this.state.currentUser || {}; }

    get appConfig() { return this.state.appConfig || {}; }

    destroy() {
        if ( this.baseRef ) {
            this.baseRef.off();
        }
    }

    onConfigLoaded( appConfig : AppConfig ) {
        this.baseRef = new Firebase( appConfig.firebaseUrl );
        this.baseRef.onAuth( authData => this.onAuth( authData, appConfig ) );
    }

    onAuth( authData, appConfig ) {
        if ( authData ) {
            this.onAuthSuccess( authData, appConfig );
        } else {
            this.onAuthFailure();
        }
    }

    onAuthSuccess( authData, appConfig ) {
        console.log(authData);
        const currentUser = {
            uid             : authData.uid,
        };
        this.state = { currentUser, appConfig };
        this.publishState();
        NotificationActions.pushNotif({
            type        : 'info',
            message     : authData.google.displayName + ' logged in!'
        });
    }

    onAuthFailure() {
        const options = {
            remember: 'sessionOnly',
            scope   : 'email'
        };
        // FIXME
        // TODO : propose other ways to authenticate : twitter, github and facebook (maybe anonymous too)
        this.baseRef.authWithOAuthRedirect( 'google', function(error) {
            if ( error ) {
                console.log( 'Login Failed !', error );
            } else {
                // We'll never get here, as the page will redirect on success.
            }
        }, options );
    }

    isCurrentUser( user ) {
        const { currentUser } = this.state;
        if ( !user || !currentUser ) {
            return false;
        }
        return currentUser.uid === user.uid;
    }

    logout() {
        this.baseRef.unauth();
    }
}

export default new AuthStore();
<commit_msg>handle login from redirect and proxy<commit_after>import { Store }                from 'airflux';
import Firebase                 from 'firebase';

import * as ConfigActions       from '../config/ConfigActions';
import * as NotificationActions from './NotificationActions';

class AuthStore extends Store {

    state = {};

    constructor() {
        super();
        ConfigActions.loadConfig.listen( this.onConfigLoaded.bind( this ) );
    }

    get currentUser() { return this.state.currentUser || {}; }

    get appConfig() { return this.state.appConfig || {}; }

    destroy() {
        if ( this.baseRef ) {
            this.baseRef.off();
        }
    }

    onConfigLoaded( appConfig : AppConfig ) {
        this.baseRef = new Firebase( appConfig.firebaseUrl );
        this.baseRef.onAuth( authData => this.onAuth( authData, appConfig ) );
    }

    onAuth( authData, appConfig ) {
        if ( authData ) {
            this.onAuthSuccess( authData, appConfig );
        } else {
            this.onAuthFailure();
        }
    }

    onAuthSuccess( authData, appConfig ) {
        console.log(authData);
        const currentUser = {
            uid             : authData.uid,
        };
        this.state = { currentUser, appConfig };
        this.publishState();
        NotificationActions.pushNotif({
            type        : 'info',
            message     : authData.google ? authData.google.displayName : authData.auth.name + ' logged in!'
        });
    }

    onAuthFailure() {
        const options = {
            remember: 'sessionOnly',
            scope   : 'email'
        };
        // FIXME
        // TODO : propose other ways to authenticate : twitter, github and facebook (maybe anonymous too)
        this.baseRef.authWithOAuthRedirect( 'google', function(error) {
            if ( error ) {
                console.log( 'Login Failed !', error );
            } else {
                // We'll never get here, as the page will redirect on success.
            }
        }, options );
    }

    isCurrentUser( user ) {
        const { currentUser } = this.state;
        if ( !user || !currentUser ) {
            return false;
        }
        return currentUser.uid === user.uid;
    }

    logout() {
        this.baseRef.unauth();
    }
}

export default new AuthStore();
<|endoftext|>


<commit_before>var mainModule = angular.module('app', []).
	controller("forumCtrl", function forumCtrl($scope, $window, $timeout, $http){
		$scope.currentProject = null;
		$scope.currentThread = null;
		$scope.show = "login";
		$scope.newProjectName = "";
		$scope.newThreadSubject = "";
		$scope.newThreadMessage = "";
		$scope.newPost = "";

		$scope.openThreads = function(project){
			$scope.show = "threads";
			$scope.currentProject = project;
		}

		$scope.openPosts = function(thread){
			$scope.show = "posts";
			$scope.currentThread = thread;
		}

		$scope.createProject = function(){
			$scope.projects.push({id: $scope.projects.length, name: $scope.newProjectName, threads: []});
			$scope.newProjectName = "";
			$scope.show="projects";
		}

		$scope.createThread = function(){
			$scope.currentProject.threads.push({
				id: $scope.currentProject.threads.length,
				subject: $scope.newThreadSubject,
				posts: [{id: 0, userID: 0, message: $scope.newThreadMessage}]
			})
			$scope.newThreadSubject = "";
			$scope.newThreadMessage = "";
			$scope.show="threads";
		}

		$scope.sendPost = function(){
			$scope.currentThread.posts.push({
				id: $scope.currentThread.posts.length,
				userID: 2,
				message: $scope.newPost
			});

			$scope.newPost= "";
		}

		$scope.users = [
			{
				id: 0,
				firstName: "Michal",
				surname: "Paszkiewicz",
				imageURL: "http://www.gravatar.com/avatar/f8231ccab5f14c6499e32e17700399d9?d=wavatar",
				websiteURL: "http://www.michalpaszkiewicz.co.uk",
				experience: "8 years live guitar playing",
				skills: "Software",
				openToPublic: false
			},
			{
				id: 1,
				firstName: "Aaron",
				surname: "Dennis",
				imageURL: "https://avatars3.githubusercontent.com/u/9275082?v=3&amp",
				websiteURL: "https://aaronjden.github.io",
				experience: "Lots of good experience",
				skills: "Software",
				openToPublic: true
			},
			{
				id: 2,
				firstName: "Dave",
				surname: "Da rave",
				imageURL: "https://avatars2.githubusercontent.com/u/8076321?v=3&amp",
				websiteURL: "http://tametheboardgame.com/",
				experience: "40 years in the circus industry",
				skills: "Juggling",
				openToPublic: true
			}
		];

		$scope.getItemFromID = function(array, id)
		{
			for(var i = 0; i < array.length; i++){
				if(array[i].id == id){
					return array[i];
				}
			}
			return false;
		}

		$scope.getIndexFromID = function(array, id)
		{
			for(var i = 0; i < array.length; i++){
				if(array[i].id == id){
					return i;
				}
			}
			return false;
		}

		$scope.projects = [
			{
				id: 0,
				name: "Prince of Egypt",
				threads: [
					{
						id: 0,
						projectID: 0,
						subject: "when to do this",
						posts: [
							{
								id: 0,
								userID: 0,
								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							},
							{
								id: 1,
								userID: 2,
								threadID: 0,								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							}
						]
					},
					{
						id: 1,
						projectID: 0,
						subject: "stuff and things",
						posts: [
							{
								id: 2,
								userID: 1,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "yo"
							},
							{
								id: 3,
								userID: 2,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "kazaa"
							}
						]
					}
				]
			},
			{
				id: 1,
				name: "The Lion King",
				threads: [
					{
						id: 0,
						projectID: 0,
						subject: "when to do this",
						posts: [
							{
								id: 0,
								userID: 0,
								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							},
							{
								id: 1,
								userID: 2,
								threadID: 0,								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							}
						]
					},
					{
						id: 1,
						projectID: 0,
						subject: "stuff and things",
						posts: [
							{
								id: 2,
								userID: 1,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "yo"
							},
							{
								id: 3,
								userID: 2,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "kazaa"
							}
						]
					}
				]
			}
		];

	});
<commit_msg>Update forum.js<commit_after>var mainModule = angular.module('app', []).
	controller("forumCtrl", function forumCtrl($scope, $window, $timeout, $http){
		$scope.currentProject = null;
		$scope.currentThread = null;
		$scope.show = "login";
		$scope.newProjectName = "";
		$scope.newThreadSubject = "";
		$scope.newThreadMessage = "";
		$scope.newPost = "";

		$scope.openThreads = function(project){
			$scope.show = "threads";
			$scope.currentProject = project;
		}

		$scope.openPosts = function(thread){
			$scope.show = "posts";
			$scope.currentThread = thread;
		}

		$scope.createProject = function(){
			$scope.projects.push({id: $scope.projects.length, name: $scope.newProjectName, threads: []});
			$scope.newProjectName = "";
			$scope.show="projects";
		}

		$scope.createThread = function(){
			$scope.currentProject.threads.push({
				id: $scope.currentProject.threads.length,
				subject: $scope.newThreadSubject,
				posts: [{id: 0, userID: 0, message: $scope.newThreadMessage}]
			})
			$scope.newThreadSubject = "";
			$scope.newThreadMessage = "";
			$scope.show="threads";
		}

		$scope.sendPost = function(){
			$scope.currentThread.posts.push({
				id: $scope.currentThread.posts.length,
				userID: 2,
				message: $scope.newPost
			});

			$scope.newPost= "";
		}

		$scope.users = [
			{
				id: 0,
				firstName: "Michal",
				surname: "Paszkiewicz",
				imageURL: "http://www.gravatar.com/avatar/f8231ccab5f14c6499e32e17700399d9?d=wavatar",
				websiteURL: "http://www.michalpaszkiewicz.co.uk",
				experience: "8 years live guitar playing",
				skills: "Software",
				openToPublic: true
			},
			{
				id: 1,
				firstName: "Aaron",
				surname: "Dennis",
				imageURL: "https://avatars3.githubusercontent.com/u/9275082?v=3&amp",
				websiteURL: "https://aaronjden.github.io",
				experience: "Lots of good experience",
				skills: "Software",
				openToPublic: true
			},
			{
				id: 2,
				firstName: "Dave",
				surname: "Da rave",
				imageURL: "https://avatars2.githubusercontent.com/u/8076321?v=3&amp",
				websiteURL: "http://tametheboardgame.com/",
				experience: "40 years in the circus industry",
				skills: "Juggling",
				openToPublic: true
			}
		];

		$scope.getItemFromID = function(array, id)
		{
			for(var i = 0; i < array.length; i++){
				if(array[i].id == id){
					return array[i];
				}
			}
			return false;
		}

		$scope.getIndexFromID = function(array, id)
		{
			for(var i = 0; i < array.length; i++){
				if(array[i].id == id){
					return i;
				}
			}
			return false;
		}

		$scope.projects = [
			{
				id: 0,
				name: "Prince of Egypt",
				threads: [
					{
						id: 0,
						projectID: 0,
						subject: "when to do this",
						posts: [
							{
								id: 0,
								userID: 0,
								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							},
							{
								id: 1,
								userID: 2,
								threadID: 0,								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							}
						]
					},
					{
						id: 1,
						projectID: 0,
						subject: "stuff and things",
						posts: [
							{
								id: 2,
								userID: 1,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "yo"
							},
							{
								id: 3,
								userID: 2,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "kazaa"
							}
						]
					}
				]
			},
			{
				id: 1,
				name: "The Lion King",
				threads: [
					{
						id: 0,
						projectID: 0,
						subject: "when to do this",
						posts: [
							{
								id: 0,
								userID: 0,
								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							},
							{
								id: 1,
								userID: 2,
								threadID: 0,								threadID: 0,
								projectID: 0,
								date: "2014",
								message: "hi"
							}
						]
					},
					{
						id: 1,
						projectID: 0,
						subject: "stuff and things",
						posts: [
							{
								id: 2,
								userID: 1,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "yo"
							},
							{
								id: 3,
								userID: 2,
								threadID: 1,
								projectID: 0,
								date: "2014",
								message: "kazaa"
							}
						]
					}
				]
			}
		];

	});
<|endoftext|>


<commit_before>'use strict';

/* This function is borrowed from http://stackoverflow.com/a/9899701/13860 */
(function(funcName, baseObj) {
    // The public function name defaults to window.docReady
    // but you can pass in your own object and own function name and those will be used
    // if you want to put them in a different namespace
    funcName = funcName || "docReady";
    baseObj = baseObj || window;
    var readyList = [];
    var readyFired = false;
    var readyEventHandlersInstalled = false;

    // call this when the document is ready
    // this function protects itself against being called more than once
    function ready() {
        if (!readyFired) {
            // this must be set to true before we start calling callbacks
            readyFired = true;
            for (var i = 0; i < readyList.length; i++) {
                // if a callback here happens to add new ready handlers,
                // the docReady() function will see that it already fired
                // and will schedule the callback to run right after
                // this event loop finishes so all handlers will still execute
                // in order and no new ones will be added to the readyList
                // while we are processing the list
                readyList[i].fn.call(window, readyList[i].ctx);
            }
            // allow any closures held by these functions to free
            readyList = [];
        }
    }

    function readyStateChange() {
        if ( document.readyState === "complete" ) {
            ready();
        }
    }

    // This is the one public interface
    // docReady(fn, context);
    // the context argument is optional - if present, it will be passed
    // as an argument to the callback
    baseObj[funcName] = function(callback, context) {
        // if ready has already fired, then just schedule the callback
        // to fire asynchronously, but right away
        if (readyFired) {
            setTimeout(function() {callback(context);}, 1);
            return;
        } else {
            // add the function and context to the list
            readyList.push({fn: callback, ctx: context});
        }
        // if document already ready to go, schedule the ready function to run
        if (document.readyState === "complete") {
            setTimeout(ready, 1);
        } else if (!readyEventHandlersInstalled) {
            // otherwise if we don't have event handlers installed, install them
            if (document.addEventListener) {
                // first choice is DOMContentLoaded event
                document.addEventListener("DOMContentLoaded", ready, false);
                // backup is window load event
                window.addEventListener("load", ready, false);
            } else {
                // must be IE
                document.attachEvent("onreadystatechange", readyStateChange);
                window.attachEvent("onload", ready);
            }
            readyEventHandlersInstalled = true;
        }
    }
})("docReady", window);

(function(baseObj) {
    function fserve(r) {
        var path = r.path,
            type = r.content_type,
            data = r.data;
        var b = new Blob([data], {type: type})
        var url = URL.createObjectURL(b)

        var req = requests[ path ];
        var attr = data.Tag == 'LINK' ? 'href' : 'src';
        for ( var i = 0; i < req.targets.length; i++ ) {
            var target = req.targets[i];
            var attr = 'src' in target ? 'src' : 'href';
            target[attr] = url;
        }
        req.targets = [];
    }

    function sendMessage(type, payload) {
        parent.postMessage({
            type:     type,
            iframeID: window.location.href,
            payload:  payload,
        }, '*');
    }

    var requests = {};
    baseObj.addEventListener('error', function(e) {
        var t = e.target;
        if ( t === undefined ) {
            // This isn't a 404 error; so just skip it
            return true;
        }
        var path = t.tagName == 'LINK' ? t.getAttribute('href') : t.getAttribute('src');
        if ( requests[path] === undefined ) {
            requests[path] = {
                targets: []
            };
            sendMessage('fserve', path);
        }
        if ( requests[path].url === undefined ) {
            // This means we already requested the file, but it hasn't arrived yet
            requests[path].targets.push( t );
            return false;
        }
        // We've already requested the file, and we have it now!
        t.src = requests[path].url;
        return false;
    }, true);

    baseObj.addEventListener('message', function(e) {
        switch (e.data.type) {
            case "fserve":
                fserve(e.data.payload);
                break;
            case "submit":
                var form = document.getElementById('mainform');
                var input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'submit';
                input.value = e.data.payload;
                form.appendChild(input);
                form.dispatchEvent(new Event('submit'));
                break;
            default:
                if ( typeof(handleMessage) === 'function' ) {
                    handleMessage(e.data.type, e.data.payload);
                    break
                }
                console.log("Unexpected message type '" + e.data.type + "' from parent, and 'handleMessage()'' undefined")
        }
    });

    function playaudio() {
        var media = document.getElementsByTagName("audio")
        if (media.length == 0) {
            return;
        }
        // Create a 'play button' widget for the first media element (which will
        // control all other ones, too)
        var playButton = document.createTextNode("▶");
        media[0].parentNode.insertBefore(playButton, media[0].nextSibling)

        // And listen for keypress to replay audio
        document.onkeypress = function (e) {
            if (e.key == 'r' || e.key == 'R') {
                playWhenReady(media[0]);
            }
        };

        // Install 'ended' event handlers on all but the last media file, which
        // will trigger the following one to play.
        for (var i=0; i < media.length-1; i++) {
            media[i].addEventListener('ended', function() {
                playWhenReady(media[i+1]);
            });
        }
        playWhenReady(media[0]);
    }

    function playWhenReady(media) {
        // A lazy way to check if the media file has been loaded yet.
        if ( media.src.match(/^blob:/) ) {
            media.play();
            return;
        }
        media.addEventListener('canplay', function() {
            media.play();
        });
    }

    // Adapted from https://code.google.com/archive/p/form-serialize/
    function extractFormValues(form) {
        if (!form || form.nodeName !== "FORM") {
            return;
        }
        var i, j, q = [];
        for (i = form.elements.length - 1; i >= 0; i = i - 1) {
            if (form.elements[i].name === "") {
                continue;
            }
            switch (form.elements[i].nodeName) {
            case 'INPUT':
                switch (form.elements[i].type) {
                case 'text':
                case 'hidden':
                case 'password':
                case 'button':
                case 'reset':
                case 'submit':
                    q[form.elements[i].name] = form.elements[i].value;
                    break;
                case 'checkbox':
                case 'radio':
                    if (form.elements[i].checked) {
                        q[form.elements[i].name] = form.elements[i].value;
                    }
                    break;
                case 'file':
                    break;
                }
                break;
            case 'TEXTAREA':
                q[form.elements[i].name] = form.elements[i].value;
                break;
            case 'SELECT':
                switch (form.elements[i].type) {
                case 'select-one':
                    q[form.elements[i].name] = form.elements[i].value;
                    break;
                case 'select-multiple':
                    var k = [];
                    for (j = form.elements[i].options.length - 1; j >= 0; j = j - 1) {
                        if (form.elements[i].options[j].selected) {
                            k.push(form.elements[i].options[j].value);
                        }
                    }
                    q[form.elements[i].name] = k;
                    break;
                }
                break;
            case 'BUTTON':
                switch (form.elements[i].type) {
                case 'reset':
                case 'submit':
                case 'button':
                    q[form.elements[i].name] = form.elements[i].value;
                    break;
                }
                break;
            }
        }
        return q;
    }

    function processForm(e) {
        if (e.preventDefault) e.preventDefault();
        var form = document.getElementById('mainform');
        var values = extractFormValues(form);
        sendMessage('submit', values);
        return false;
    };

    docReady(function() {
        playaudio();
        var form = document.getElementById('mainform');
        // Disable autocomplete everywhere
        var inputs = form.getElementsByTagName('input')
        for ( var i = 0; i < inputs.length; i++ ) {
            inputs[i].setAttribute('autocomplete', 'off');
            inputs[i].setAttribute('autocorrect', 'off');
            inputs[i].setAttribute('autocapitalize', 'off');
            inputs[i].setAttribute('spellcheck', 'false');
        }
        // Set up form submission
        if (form.attachEvent) {
            form.attachEvent('submit', processForm);
        } else {
            form.addEventListener('submit', processForm);
        }
    });
})(window);
<commit_msg>Clicking now replays audio, too<commit_after>'use strict';

/* This function is borrowed from http://stackoverflow.com/a/9899701/13860 */
(function(funcName, baseObj) {
    // The public function name defaults to window.docReady
    // but you can pass in your own object and own function name and those will be used
    // if you want to put them in a different namespace
    funcName = funcName || "docReady";
    baseObj = baseObj || window;
    var readyList = [];
    var readyFired = false;
    var readyEventHandlersInstalled = false;

    // call this when the document is ready
    // this function protects itself against being called more than once
    function ready() {
        if (!readyFired) {
            // this must be set to true before we start calling callbacks
            readyFired = true;
            for (var i = 0; i < readyList.length; i++) {
                // if a callback here happens to add new ready handlers,
                // the docReady() function will see that it already fired
                // and will schedule the callback to run right after
                // this event loop finishes so all handlers will still execute
                // in order and no new ones will be added to the readyList
                // while we are processing the list
                readyList[i].fn.call(window, readyList[i].ctx);
            }
            // allow any closures held by these functions to free
            readyList = [];
        }
    }

    function readyStateChange() {
        if ( document.readyState === "complete" ) {
            ready();
        }
    }

    // This is the one public interface
    // docReady(fn, context);
    // the context argument is optional - if present, it will be passed
    // as an argument to the callback
    baseObj[funcName] = function(callback, context) {
        // if ready has already fired, then just schedule the callback
        // to fire asynchronously, but right away
        if (readyFired) {
            setTimeout(function() {callback(context);}, 1);
            return;
        } else {
            // add the function and context to the list
            readyList.push({fn: callback, ctx: context});
        }
        // if document already ready to go, schedule the ready function to run
        if (document.readyState === "complete") {
            setTimeout(ready, 1);
        } else if (!readyEventHandlersInstalled) {
            // otherwise if we don't have event handlers installed, install them
            if (document.addEventListener) {
                // first choice is DOMContentLoaded event
                document.addEventListener("DOMContentLoaded", ready, false);
                // backup is window load event
                window.addEventListener("load", ready, false);
            } else {
                // must be IE
                document.attachEvent("onreadystatechange", readyStateChange);
                window.attachEvent("onload", ready);
            }
            readyEventHandlersInstalled = true;
        }
    }
})("docReady", window);

(function(baseObj) {
    function fserve(r) {
        var path = r.path,
            type = r.content_type,
            data = r.data;
        var b = new Blob([data], {type: type})
        var url = URL.createObjectURL(b)

        var req = requests[ path ];
        var attr = data.Tag == 'LINK' ? 'href' : 'src';
        for ( var i = 0; i < req.targets.length; i++ ) {
            var target = req.targets[i];
            var attr = 'src' in target ? 'src' : 'href';
            target[attr] = url;
        }
        req.targets = [];
    }

    function sendMessage(type, payload) {
        parent.postMessage({
            type:     type,
            iframeID: window.location.href,
            payload:  payload,
        }, '*');
    }

    var requests = {};
    baseObj.addEventListener('error', function(e) {
        var t = e.target;
        if ( t === undefined ) {
            // This isn't a 404 error; so just skip it
            return true;
        }
        var path = t.tagName == 'LINK' ? t.getAttribute('href') : t.getAttribute('src');
        if ( requests[path] === undefined ) {
            requests[path] = {
                targets: []
            };
            sendMessage('fserve', path);
        }
        if ( requests[path].url === undefined ) {
            // This means we already requested the file, but it hasn't arrived yet
            requests[path].targets.push( t );
            return false;
        }
        // We've already requested the file, and we have it now!
        t.src = requests[path].url;
        return false;
    }, true);

    baseObj.addEventListener('message', function(e) {
        switch (e.data.type) {
            case "fserve":
                fserve(e.data.payload);
                break;
            case "submit":
                var form = document.getElementById('mainform');
                var input = document.createElement('input');
                input.type = 'hidden';
                input.name = 'submit';
                input.value = e.data.payload;
                form.appendChild(input);
                form.dispatchEvent(new Event('submit'));
                break;
            default:
                if ( typeof(handleMessage) === 'function' ) {
                    handleMessage(e.data.type, e.data.payload);
                    break
                }
                console.log("Unexpected message type '" + e.data.type + "' from parent, and 'handleMessage()'' undefined")
        }
    });

    function playaudio() {
        var media = document.getElementsByTagName("audio")
        if (media.length == 0) {
            return;
        }
        // Create a 'play button' widget for the first media element (which will
        // control all other ones, too)
        var playButton = document.createTextNode("▶");
        playButton.id = "playButton";
        media[0].parentNode.insertBefore(playButton, media[0].nextSibling)

        // And listen for keypress to replay audio
        document.onkeypress = function (e) {
            if (document.activeElement.tagName == 'INPUT') {
                return;
            }
            if (e.key == 'r' || e.key == 'R') {
                playWhenReady(media[0]);
            }
        };
        document.body.onclick = function(e) {
            if (e.target.tagName == 'INPUT') {
                return;
            }
            playWhenReady(media[0]);
        };

        // Install 'ended' event handlers on all but the last media file, which
        // will trigger the following one to play.
        for (var i=0; i < media.length-1; i++) {
            media[i].addEventListener('ended', function() {
                playWhenReady(media[i+1]);
            });
        }
        playWhenReady(media[0]);
    }

    function playWhenReady(media) {
        // A lazy way to check if the media file has been loaded yet.
        if ( media.src.match(/^blob:/) ) {
            media.play();
            return;
        }
        media.addEventListener('canplay', function() {
            media.play();
        });
    }

    // Adapted from https://code.google.com/archive/p/form-serialize/
    function extractFormValues(form) {
        if (!form || form.nodeName !== "FORM") {
            return;
        }
        var i, j, q = [];
        for (i = form.elements.length - 1; i >= 0; i = i - 1) {
            if (form.elements[i].name === "") {
                continue;
            }
            switch (form.elements[i].nodeName) {
            case 'INPUT':
                switch (form.elements[i].type) {
                case 'text':
                case 'hidden':
                case 'password':
                case 'button':
                case 'reset':
                case 'submit':
                    q[form.elements[i].name] = form.elements[i].value;
                    break;
                case 'checkbox':
                case 'radio':
                    if (form.elements[i].checked) {
                        q[form.elements[i].name] = form.elements[i].value;
                    }
                    break;
                case 'file':
                    break;
                }
                break;
            case 'TEXTAREA':
                q[form.elements[i].name] = form.elements[i].value;
                break;
            case 'SELECT':
                switch (form.elements[i].type) {
                case 'select-one':
                    q[form.elements[i].name] = form.elements[i].value;
                    break;
                case 'select-multiple':
                    var k = [];
                    for (j = form.elements[i].options.length - 1; j >= 0; j = j - 1) {
                        if (form.elements[i].options[j].selected) {
                            k.push(form.elements[i].options[j].value);
                        }
                    }
                    q[form.elements[i].name] = k;
                    break;
                }
                break;
            case 'BUTTON':
                switch (form.elements[i].type) {
                case 'reset':
                case 'submit':
                case 'button':
                    q[form.elements[i].name] = form.elements[i].value;
                    break;
                }
                break;
            }
        }
        return q;
    }

    function processForm(e) {
        if (e.preventDefault) e.preventDefault();
        var form = document.getElementById('mainform');
        var values = extractFormValues(form);
        sendMessage('submit', values);
        return false;
    };

    docReady(function() {
        playaudio();
        var form = document.getElementById('mainform');
        // Disable autocomplete everywhere
        var inputs = form.getElementsByTagName('input')
        for ( var i = 0; i < inputs.length; i++ ) {
            inputs[i].setAttribute('autocomplete', 'off');
            inputs[i].setAttribute('autocorrect', 'off');
            inputs[i].setAttribute('autocapitalize', 'off');
            inputs[i].setAttribute('spellcheck', 'false');
        }
        // Set up form submission
        if (form.attachEvent) {
            form.attachEvent('submit', processForm);
        } else {
            form.addEventListener('submit', processForm);
        }
    });
})(window);
<|endoftext|>


<commit_before>"use strict";
/*jslint browser: true*/
/*global Tangram, gui */

// initialize variables
var map, views, queue, nextView,
    newImg, newCanvas, newCtx, newData,
    oldImg, oldCanvas, oldCtx, oldData,
    diffImg, diffCanvas, diffCtx, diff,
    images = {};
var testsFile = "./views.json";
var imgDir = "images/";
var imgType = ".png";
var size = 250; // physical pixels
var lsize = 250 * window.devicePixelRatio; // logical pixels
var scores = [], totalScore = 0;
var tests = document.getElementById("tests");
var statusDiv = document.getElementById("status");
var totalScoreDiv = document.getElementById("totalScore");

// load file
function readTextFile(file, callback) {
    var rawFile = new XMLHttpRequest();
    rawFile.overrideMimeType("application/json");
    rawFile.open("GET", file, true);
    rawFile.onreadystatechange = function() {
        if (rawFile.readyState === 4 && rawFile.status == "200") {
            callback(rawFile.responseText);
        }
    }
    rawFile.send(null);
}

function parseView(view) {
    // console.log('view:', view);
    if (Object.prototype.toString.call(view["location"]) === '[object Array]') {
        return view; // no parsing needed
    } else if (typeof(view["location"]) === "string") {
        // parse string location as array of floats
        var location = view["location"].split(/[ ,]+/);
        location = location.map(parseFloat);
        view["location"] = location;
        return view;
    } else {
        console.log("Can't parse location:", view);
    }
}

// setup divs and canvases
var prep = new Promise( function (resolve, reject) {

    // load and parse test json
    readTextFile(testsFile, function(text){
        var data = JSON.parse(text);
        // convert tests to an array for easier traversal
        views = Object.keys(data.tests).map(function (key) {
            // add test's name as a property of the test
            data.tests[key].name = key;
            return data.tests[key];
        });

        // clone views array
        queue = views.slice(0);
        nextView = parseView(queue.shift()); // pop first and parse location

        // then initialize Tangram with the first view
        map = (function () {
            // console.log('views:', views);
            // console.log('nextView', nextView.location);
            var map_start_location = nextView.location;

            /*** Map ***/

            var map = L.map('map', {
                keyboardZoomOffset : .05,
                zoomControl: false,
                attributionControl : false
            });

            var layer = Tangram.leafletLayer({
                scene: nextView.url,
                // highDensityDisplay: false
            });

            window.layer = layer;
            var scene = layer.scene;
            window.scene = scene;

            // setView expects format ([lat, long], zoom)
            map.setView(map_start_location.slice(0, 3), map_start_location[2]);

            layer.addTo(map);

            return map;

        }());

        // subscribe to Tangram's published view_complete event to
        // load the next scene when the current scene is done drawing
        scene.subscribe({
            view_complete: function () {
                // console.log('nextView:', nextView);
                if (nextView) {
                    // when prep is done, screenshot is made, and oldImg is loaded...
                    Promise.all([prep,screenshot(true),loadOld(imgDir+nextView.name+imgType)]).then(function() {
                        // perform the diff
                        doDiff(nextView);
                        // move along
                        if (queue.length > 0) {
                            nextView = parseView(queue.shift()); // pop first and parse location
                            loadView(nextView);
                        } else return;
                    });
                } else {
                    console.log('view_complete, no nextView');
                    return viewComplete();
                }
            }
        });

        // set page title
        document.getElementById('title').innerHTML = testsFile;

    });

    // set sizes
    document.getElementById("map").style.height = size+"px";
    document.getElementById("map").style.width = size+"px";

    // set up canvases

    // make canvas for the old image
    oldCanvas = document.createElement('canvas');
    oldCanvas.height = lsize;
    oldCanvas.width = lsize;
    oldCtx = oldCanvas.getContext('2d');

    // make a canvas for the newly-drawn map image
    newCanvas = document.createElement('canvas');
    newCanvas.height = lsize;
    newCanvas.width = lsize;
    newCtx = newCanvas.getContext('2d');

    // make a canvas for the diff
    diffCanvas = document.createElement('canvas');
    diffCanvas.height = lsize;
    diffCanvas.width = lsize;
    diffCtx = diffCanvas.getContext('2d');
    diff = diffCtx.createImageData(lsize, lsize);

    resolve();
});

// load an image asynchronously with a Promise
function loadImage (url, target) {
    return new Promise(function(resolve, reject) {
        var image = target || new Image();
        image.onload = function() {
            resolve({ url: url, image: image });
        };
        image.onerror = function(error) {
            resolve({ error: error });
        };
        image.crossOrigin = 'anonymous';
        // force-refresh any local images with a cache-buster
        if (url.slice(-4) == imgType) url += "?" + new Date().getTime();
        image.src = url;
    });
}

// load the old image
function loadOld (img) {
    oldImg = new Image();
    return loadImage(img).then(function(result){
        if (result.url) {
            // set the old image to be drawn to the canvas once the image loads
            oldImg = result.image;
            oldCtx.drawImage(oldImg, 0, 0, oldImg.width, oldImg.height, 0, 0, oldCanvas.width, oldCanvas.height);
            // make the data available to pixelmatch
            oldData = oldCtx.getImageData(0, 0, lsize, lsize);
        } else {
            oldData = null;
        }
        return result;
    });
};

// take a screenshot
function screenshot (save) {
    return scene.screenshot().then(function(data) {
        // save it to a file
        if (save) saveAs(data.blob, nextView.name);

        var urlCreator = window.URL || window.webkitURL;
        newImg = new Image();
        return loadImage(urlCreator.createObjectURL( data.blob ), newImg);
    });
};

// perform the image comparison and update the html
function doDiff( test ) {
    // save the new image to the new canvas, stretching it to fit (in case it's retina)
    newCtx.drawImage(newImg, 0, 0, newImg.width, newImg.height, 0, 0, newCanvas.width, newCanvas.height);
    // make the data available
    var newData = newCtx.getImageData(0, 0, lsize, lsize);
    if (oldData) {
        // run the diff
        var difference = pixelmatch(newData.data, oldData.data, diff.data, lsize, lsize, {threshold: 0.1});
        // calculate match percentage
        var match = 100-(difference/(lsize*lsize)*100*100)/100;
        var matchScore = Math.floor(match);
        // put the diff in its canvas
        diffCtx.putImageData(diff, 0, 0);
    } else {
        // generating new image
        match = 100;
        matchScore = "";
    }

    // UPDATE READOUTS
    var count = views.length-queue.length;
    statusDiv.innerHTML = count + " of " + views.length;

    // update master percentage
    scores[test.name] = match;
    var totalSum = 0;
    for (var v in scores) {
        totalSum += scores[v];
    }
    totalScore = Math.floor(totalSum/(100*count)*100);
    var threatLevel = totalScore > 99 ? "green" : totalScore > 98 ? "orange" : "red";
    totalScoreDiv.innerHTML = "
"+totalScore+"% match

";

    // make an output row
    makeRow(test, matchScore);

    images[test.name] = {};
    images[test.name].oldImg = oldImg;
    images[test.name].newImg = newImg;
    images[test.name].diffImg = diffImg;
};

function loadView (view) {
    // load and draw scene
    scene.load(view.url).then(function() {
        if (!view) return;
        scene.animated = false;
        map.setView([view.location[0], view.location[1]], view.location[2]);
        scene.requestRedraw();
    });
}

// save a file with a POST request to the server
function saveAs( file, filename ) {
    var url = '/save';
    var data = new FormData();

    data.append("image", file, filename);

    var xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.onload = function () {
        // console.log('response:', this.responseText);
        // window.location.href = ".#"+this.responseText;
    };
    xhr.send(data);
}

function stop() {
    console.log('stop button');
    nextView = false;
    queue = false;
}

// convert event to promise
function viewComplete () {
    console.log('viewComplete');
    return new Promise(function(resolve, reject) {
        console.log('viewComplete sent');
        resolve();
    });
}

function makeRow(test, matchScore) {
    // check to see if div already exists (if re-running a test);
    var testdiv = document.getElementById(test.name);

    // if a row for this test doesn't already exist:
    if (testdiv === null) {
        // generate one
        var testdiv = document.createElement('div');
        testdiv.className = 'test';
        testdiv.id = test.name;
        tests.insertBefore(testdiv, tests.firstChild);
    } else {
        // clear it out
        testdiv.innerHTML = "";
    }
    testdiv.style.minHeight = size+"px";

    var title = document.createElement('div');
    title.className = 'testname';
    title.innerHTML = test.name;
    testdiv.appendChild(title);

    var oldcolumn = document.createElement('span');
    oldcolumn.className = 'column';
    oldcolumn.id = "old";
    oldcolumn.innerHTML = "old";
    testdiv.appendChild(oldcolumn);

    var newcolumn = document.createElement('span');
    newcolumn.className = 'column';
    newcolumn.id = "new";
    newcolumn.innerHTML = "new";
    testdiv.appendChild(newcolumn);

    var diffcolumn = document.createElement('span');
    diffcolumn.className = 'column';
    diffcolumn.id = "diff";
    diffcolumn.innerHTML = "diff";
    testdiv.appendChild(diffcolumn);

    // insert old and new images
    newImg.width = size;
    newImg.height = size;
    newcolumn.appendChild( newImg );

    oldImg.width = size;
    oldImg.height = size;
    oldcolumn.appendChild( oldImg );

    // CONTROLS //

    var controls = document.createElement('div');
    controls.className = 'controls';
    testdiv.appendChild(controls);

    var threatLevel = matchScore > 99 ? "green" : matchScore > 95 ? "orange" : "red";

    if (matchScore != "") {
        matchScore += "% match";
        diffImg = document.createElement('img');
        diffImg.src = diffCanvas.toDataURL("image/png");
        diffImg.width = size;
        diffImg.height = size;
        diffcolumn.appendChild( diffImg );
    }

    controls.innerHTML = "
"+matchScore+"

";

    var refreshButton =  document.createElement('button');
    refreshButton.innerHTML = "refresh " + test.name;
    controls.appendChild(refreshButton);
    refreshButton.onclick = function() {refresh(test);}

    var exportGifButton =  document.createElement('button');
    exportGifButton.innerHTML = "export gif";
    // controls.appendChild(exportGifButton);

    var exportButton =  document.createElement('button');
    exportButton.innerHTML = "export " + test.name;
    // store current value of these global variables
    exportButton.onclick = function() {
        var img = makeStrip([images[test.name].oldImg, images[test.name].newImg, images[test.name].diffImg], lsize);
        popup(img);
    };
    controls.appendChild(exportButton);

}

function makeStrip(images, size) {
    var c = document.createElement('canvas');
    c.width = size*images.length;
    c.height = size;
    var ctx=c.getContext("2d");
    for (var x in images) {
        ctx.drawImage(images[x], size * x, 0, size, size);
    }
    var result = c.toDataURL("image/png");
    var z = window.devicePixelRatio;
    if (z != 1.0) { // handle retina etc
        var temp = new Image();
        return result;
        temp.src = result;
        c.width = size*images.length/z;
        c.height = size/z;
        ctx.drawImage(temp, 0, 0, c.width, c.height);
        result = c.toDataURL("image/png");
    }
    return result;
}

function popup(img) {
    var data = '';
    var myWindow = window.open("data:text/html," + encodeURIComponent(data));
}

function makeContactSheet() {
    var c = document.createElement('canvas');
    c.width = size*3;
    c.height = size*views.length;
    var ctx=c.getContext("2d");
    var i = 0;
    for (var x in images) {
        var img = new Image();
        if (!images.hasOwnProperty(x)) continue; // sigh
        var strip = makeStrip([images[x].oldImg, images[x].newImg, images[x].diffImg], size);
        img.src = strip;
        ctx.drawImage(img, 0, size * i, size * 3, size);
        i++;
    }
    var sheet = c.toDataURL("image/png");
    popup(sheet);
    // return c.toDataURL("image/png");
}

function rerunAll() {
    console.log('refresh all button');
    tests.innerHTML = "";
    totalScoreDiv.innerHTML = "";
    nextView = false;
    totalSum = 0;
    totalScore = 0;
    queue = views.slice(0);
    startRender();
}

function refresh(test) {
    queue.push(test);
    startRender();
}

function startRender() {
    // if map is rendering, wait for it to finish, then start over
    Promise.all([viewComplete]).then(function() {
        // move along
        nextView = queue.shift()
        loadView(nextView);
    });
}

<commit_msg>retina contact sheets<commit_after>"use strict";
/*jslint browser: true*/
/*global Tangram, gui */

// initialize variables
var map, views, queue, nextView,
    newImg, newCanvas, newCtx, newData,
    oldImg, oldCanvas, oldCtx, oldData,
    diffImg, diffCanvas, diffCtx, diff,
    images = {};
var testsFile = "./views.json";
var imgDir = "images/";
var imgType = ".png";
var size = 250; // physical pixels
var lsize = 250 * window.devicePixelRatio; // logical pixels
var scores = [], totalScore = 0;
var tests = document.getElementById("tests");
var statusDiv = document.getElementById("status");
var totalScoreDiv = document.getElementById("totalScore");

// load file
function readTextFile(file, callback) {
    var rawFile = new XMLHttpRequest();
    rawFile.overrideMimeType("application/json");
    rawFile.open("GET", file, true);
    rawFile.onreadystatechange = function() {
        if (rawFile.readyState === 4 && rawFile.status == "200") {
            callback(rawFile.responseText);
        }
    }
    rawFile.send(null);
}

function parseView(view) {
    // console.log('view:', view);
    if (Object.prototype.toString.call(view["location"]) === '[object Array]') {
        return view; // no parsing needed
    } else if (typeof(view["location"]) === "string") {
        // parse string location as array of floats
        var location = view["location"].split(/[ ,]+/);
        location = location.map(parseFloat);
        view["location"] = location;
        return view;
    } else {
        console.log("Can't parse location:", view);
    }
}

// setup divs and canvases
var prep = new Promise( function (resolve, reject) {

    // load and parse test json
    readTextFile(testsFile, function(text){
        var data = JSON.parse(text);
        // convert tests to an array for easier traversal
        views = Object.keys(data.tests).map(function (key) {
            // add test's name as a property of the test
            data.tests[key].name = key;
            return data.tests[key];
        });

        // clone views array
        queue = views.slice(0);
        nextView = parseView(queue.shift()); // pop first and parse location

        // then initialize Tangram with the first view
        map = (function () {
            // console.log('views:', views);
            // console.log('nextView', nextView.location);
            var map_start_location = nextView.location;

            /*** Map ***/

            var map = L.map('map', {
                keyboardZoomOffset : .05,
                zoomControl: false,
                attributionControl : false
            });

            var layer = Tangram.leafletLayer({
                scene: nextView.url,
                // highDensityDisplay: false
            });

            window.layer = layer;
            var scene = layer.scene;
            window.scene = scene;

            // setView expects format ([lat, long], zoom)
            map.setView(map_start_location.slice(0, 3), map_start_location[2]);

            layer.addTo(map);

            return map;

        }());

        // subscribe to Tangram's published view_complete event to
        // load the next scene when the current scene is done drawing
        scene.subscribe({
            view_complete: function () {
                // console.log('nextView:', nextView);
                if (nextView) {
                    // when prep is done, screenshot is made, and oldImg is loaded...
                    Promise.all([prep,screenshot(true),loadOld(imgDir+nextView.name+imgType)]).then(function() {
                        // perform the diff
                        doDiff(nextView);
                        // move along
                        if (queue.length > 0) {
                            nextView = parseView(queue.shift()); // pop first and parse location
                            loadView(nextView);
                        } else return;
                    });
                } else {
                    console.log('view_complete, no nextView');
                    return viewComplete();
                }
            }
        });

        // set page title
        document.getElementById('title').innerHTML = testsFile;

    });

    // set sizes
    document.getElementById("map").style.height = size+"px";
    document.getElementById("map").style.width = size+"px";

    // set up canvases

    // make canvas for the old image
    oldCanvas = document.createElement('canvas');
    oldCanvas.height = lsize;
    oldCanvas.width = lsize;
    oldCtx = oldCanvas.getContext('2d');

    // make a canvas for the newly-drawn map image
    newCanvas = document.createElement('canvas');
    newCanvas.height = lsize;
    newCanvas.width = lsize;
    newCtx = newCanvas.getContext('2d');

    // make a canvas for the diff
    diffCanvas = document.createElement('canvas');
    diffCanvas.height = lsize;
    diffCanvas.width = lsize;
    diffCtx = diffCanvas.getContext('2d');
    diff = diffCtx.createImageData(lsize, lsize);

    resolve();
});

// load an image asynchronously with a Promise
function loadImage (url, target) {
    return new Promise(function(resolve, reject) {
        var image = target || new Image();
        image.onload = function() {
            resolve({ url: url, image: image });
        };
        image.onerror = function(error) {
            resolve({ error: error });
        };
        image.crossOrigin = 'anonymous';
        // force-refresh any local images with a cache-buster
        if (url.slice(-4) == imgType) url += "?" + new Date().getTime();
        image.src = url;
    });
}

// load the old image
function loadOld (img) {
    oldImg = new Image();
    return loadImage(img).then(function(result){
        if (result.url) {
            // set the old image to be drawn to the canvas once the image loads
            oldImg = result.image;
            oldCtx.drawImage(oldImg, 0, 0, oldImg.width, oldImg.height, 0, 0, oldCanvas.width, oldCanvas.height);
            // make the data available to pixelmatch
            oldData = oldCtx.getImageData(0, 0, lsize, lsize);
        } else {
            oldData = null;
        }
        return result;
    });
};

// take a screenshot
function screenshot (save) {
    return scene.screenshot().then(function(data) {
        // save it to a file
        if (save) saveAs(data.blob, nextView.name);

        var urlCreator = window.URL || window.webkitURL;
        newImg = new Image();
        return loadImage(urlCreator.createObjectURL( data.blob ), newImg);
    });
};

// perform the image comparison and update the html
function doDiff( test ) {
    // save the new image to the new canvas, stretching it to fit (in case it's retina)
    newCtx.drawImage(newImg, 0, 0, newImg.width, newImg.height, 0, 0, newCanvas.width, newCanvas.height);
    // make the data available
    var newData = newCtx.getImageData(0, 0, lsize, lsize);
    if (oldData) {
        // run the diff
        var difference = pixelmatch(newData.data, oldData.data, diff.data, lsize, lsize, {threshold: 0.1});
        // calculate match percentage
        var match = 100-(difference/(lsize*lsize)*100*100)/100;
        var matchScore = Math.floor(match);
        // put the diff in its canvas
        diffCtx.putImageData(diff, 0, 0);
    } else {
        // generating new image
        match = 100;
        matchScore = "";
    }

    // UPDATE READOUTS
    var count = views.length-queue.length;
    statusDiv.innerHTML = count + " of " + views.length;

    // update master percentage
    scores[test.name] = match;
    var totalSum = 0;
    for (var v in scores) {
        totalSum += scores[v];
    }
    totalScore = Math.floor(totalSum/(100*count)*100);
    var threatLevel = totalScore > 99 ? "green" : totalScore > 98 ? "orange" : "red";
    totalScoreDiv.innerHTML = "
"+totalScore+"% match

";

    // make an output row
    makeRow(test, matchScore);

    images[test.name] = {};
    images[test.name].oldImg = oldImg;
    images[test.name].newImg = newImg;
    images[test.name].diffImg = diffImg;
};

function loadView (view) {
    // load and draw scene
    scene.load(view.url).then(function() {
        if (!view) return;
        scene.animated = false;
        map.setView([view.location[0], view.location[1]], view.location[2]);
        scene.requestRedraw();
    });
}

// save a file with a POST request to the server
function saveAs( file, filename ) {
    var url = '/save';
    var data = new FormData();

    data.append("image", file, filename);

    var xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.onload = function () {
        // console.log('response:', this.responseText);
        // window.location.href = ".#"+this.responseText;
    };
    xhr.send(data);
}

function stop() {
    console.log('stop button');
    nextView = false;
    queue = false;
}

// convert event to promise
function viewComplete () {
    console.log('viewComplete');
    return new Promise(function(resolve, reject) {
        console.log('viewComplete sent');
        resolve();
    });
}

function makeRow(test, matchScore) {
    // check to see if div already exists (if re-running a test);
    var testdiv = document.getElementById(test.name);

    // if a row for this test doesn't already exist:
    if (testdiv === null) {
        // generate one
        var testdiv = document.createElement('div');
        testdiv.className = 'test';
        testdiv.id = test.name;
        tests.insertBefore(testdiv, tests.firstChild);
    } else {
        // clear it out
        testdiv.innerHTML = "";
    }
    testdiv.style.minHeight = size+"px";

    var title = document.createElement('div');
    title.className = 'testname';
    title.innerHTML = test.name;
    testdiv.appendChild(title);

    var oldcolumn = document.createElement('span');
    oldcolumn.className = 'column';
    oldcolumn.id = "old";
    oldcolumn.innerHTML = "old";
    testdiv.appendChild(oldcolumn);

    var newcolumn = document.createElement('span');
    newcolumn.className = 'column';
    newcolumn.id = "new";
    newcolumn.innerHTML = "new";
    testdiv.appendChild(newcolumn);

    var diffcolumn = document.createElement('span');
    diffcolumn.className = 'column';
    diffcolumn.id = "diff";
    diffcolumn.innerHTML = "diff";
    testdiv.appendChild(diffcolumn);

    // insert old and new images
    newImg.width = size;
    newImg.height = size;
    newcolumn.appendChild( newImg );

    oldImg.width = size;
    oldImg.height = size;
    oldcolumn.appendChild( oldImg );

    // CONTROLS //

    var controls = document.createElement('div');
    controls.className = 'controls';
    testdiv.appendChild(controls);

    var threatLevel = matchScore > 99 ? "green" : matchScore > 95 ? "orange" : "red";

    if (matchScore != "") {
        matchScore += "% match";
        diffImg = document.createElement('img');
        diffImg.src = diffCanvas.toDataURL("image/png");
        diffImg.width = size;
        diffImg.height = size;
        diffcolumn.appendChild( diffImg );
    }

    controls.innerHTML = "
"+matchScore+"

";

    var refreshButton =  document.createElement('button');
    refreshButton.innerHTML = "refresh " + test.name;
    controls.appendChild(refreshButton);
    refreshButton.onclick = function() {refresh(test);}

    var exportGifButton =  document.createElement('button');
    exportGifButton.innerHTML = "export gif";
    // controls.appendChild(exportGifButton);

    var exportButton =  document.createElement('button');
    exportButton.innerHTML = "export " + test.name;
    // store current value of these global variables
    exportButton.onclick = function() {
        var img = makeStrip([images[test.name].oldImg, images[test.name].newImg, images[test.name].diffImg], lsize);
        popup(img, size * 3, size);
    };
    controls.appendChild(exportButton);

}

function makeStrip(images, size) {
    var c = document.createElement('canvas');
    c.width = size*images.length;
    c.height = size;
    var ctx=c.getContext("2d");
    for (var x in images) {
        ctx.drawImage(images[x], size * x, 0, size, size);
    }
    return c.toDataURL("image/png");
}

function popup(img, width, height) {
    console.log('window.devicePixelRatio:', size/window.devicePixelRatio);

    var data = '';
    var myWindow = window.open("data:text/html," + encodeURIComponent(data));
}

function makeContactSheet() {
    var c = document.createElement('canvas');
    c.width = lsize*3;
    c.height = lsize*views.length;
    var ctx=c.getContext("2d");
    var i = 0;
    for (var x in images) {
        var img = new Image();
        if (!images.hasOwnProperty(x)) continue; // sigh
        var strip = makeStrip([images[x].oldImg, images[x].newImg, images[x].diffImg], lsize);
        img.src = strip;
        ctx.drawImage(img, 0, lsize * i, lsize * 3, lsize);
        i++;
    }
    var sheet = c.toDataURL("image/png");
    popup(sheet, size * 3, size * images.length);
}

function rerunAll() {
    console.log('refresh all button');
    tests.innerHTML = "";
    totalScoreDiv.innerHTML = "";
    nextView = false;
    totalSum = 0;
    totalScore = 0;
    queue = views.slice(0);
    startRender();
}

function refresh(test) {
    queue.push(test);
    startRender();
}

function startRender() {
    // if map is rendering, wait for it to finish, then start over
    Promise.all([viewComplete]).then(function() {
        // move along
        nextView = queue.shift()
        loadView(nextView);
    });
}

<|endoftext|>


<commit_before>var app = require('ampersand-app');
var ContentView = require('./widget-content');
var filterItemView = require('./filteritem.js');
var templates = require('../templates');
var d3 = require('d3');
var util = require('../util');
var chroma = require('chroma-js');
var dc = require('dc');

var margin = {top: 10, bottom: 20, left: 30, right: 50}; // default margins from DC

var setupLinearRegression = function (view) {
    // Setup the map/reduce for the simple linear regression
    var xid = view.model.filter.toLowerCase();
    var yid = view.model.secondary.toLowerCase();

    var reduceAdd = function (p,v) {
        if( util.validateFloat(v[xid]) != Infinity && util.validateFloat(v[yid]) != Infinity ) {
            p.count++;
            p.xsum += v[xid];
            p.ysum += v[yid];
            p.xysum += v[xid] * v[yid];
            p.xxsum += v[xid] * v[xid];
            p.yysum += v[yid] * v[yid];
        }
        return p;
    };

    var reduceRemove = function (p,v) {
        if( util.validateFloat(v[xid]) != Infinity && util.validateFloat(v[yid]) != Infinity ) {
            p.count--;
            p.xsum -= v[xid];
            p.ysum -= v[yid];
            p.xysum -= v[xid] * v[yid];
            p.xxsum -= v[xid] * v[xid];
            p.yysum -= v[yid] * v[yid];
        }
        return p;
    };

    var reduceInitial = function () {
        return {
            count: 0,
            xsum: 0,
            ysum: 0,
            xysum: 0,
            xxsum: 0,
            yysum: 0,
        };
    };

    // Setup grouping
    var group = view._dy.groupAll();
    group.reduce(reduceAdd, reduceRemove, reduceInitial);

    return group;
};

var setupPlot = function (view) {
    var el = view.queryByHook('scatter-plot');
    var height = 250;
    var width = parseInt(el.offsetWidth);

    // add the graph to the body of the webpage
    var main = d3.select(el).append("div")
        .attr("width", width)
        .attr("height", height)
        .style('position', 'relative');

    var canvas = main.append("canvas")
        .attr("width", width)
        .attr("height", height)
        .node().getContext('2d');

    var svg = main.append("svg")
        .style("position", "absolute")
        .style("top", "0px")
        .style("left", "0px")
        .style("z-index", 2)
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var colorscale = chroma.scale(["#022A08", "#35FE57"]);

    var xrange = window.app.filters.get(view.model.filter)._range;
    var yrange = window.app.filters.get(view.model.secondary)._range;

    // Allow a no-color plot
    var zrange = [0,1];
    if(window.app.filters.get(view.model.color)) {
        zrange = window.app.filters.get(view.model.color)._range;
    }

    var xScale = d3.scale.linear().domain(xrange).range([0,width - margin.left - margin.right]);
    var yScale = d3.scale.linear().domain(yrange).range([height - margin.top - margin.bottom,0]);
    var zScale = d3.scale.linear().domain(zrange).range([0,1]);

    var xMap = function (d) {
        var v = util.validateFloat(d[view.model.filter.toLowerCase()]);
        if(isNaN(v) || v == Infinity) return -99999;
        return xScale(v);
    };
    var yMap = function (d) {
        var v = util.validateFloat(d[view.model.secondary.toLowerCase()]);
        if(isNaN(v) || v == Infinity) return -99999;
        return yScale(v);
    };
    var zMap = function (d) {
        var v = util.validateFloat(d[view.model.color.toLowerCase()]);
        if(isNaN(v) || v == Infinity) return chroma('gray').rgba();
        return colorscale(zScale(v)).rgba();
    };

    // x-axis
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + (height - margin.bottom - margin.top) + ")")
        .call(d3.svg.axis().scale(xScale).orient("bottom"))
        .append("text")
        .attr("class", "label")
        .attr("x", width - margin.left - margin.right)
        .attr("y", -6)
        .style("text-anchor", "end")
        .text(view.model.filter);

    // y-axis
    svg.append("g")
        .attr("class", "y axis")
        .call(d3.svg.axis().scale(yScale).orient("left"))
        .append("text")
        .attr("class", "label")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", ".71em")
        .style("text-anchor", "end")
        .text(view.model.secondary);

    // Fitted line
    svg.append("g")
        .attr("class", "regline")
        .append("line")
        .attr("x1", xScale(xrange[0]))
        .attr("x2", xScale(xrange[1]))
        .attr("y1", yScale(yrange[0]))
        .attr("y2", yScale(yrange[0]))
        .attr("stroke-width", 0)
        .attr("stroke", "black");

    if (view._reggroup) {
        view._reggroup.dispose();
    }
    view._reggroup = setupLinearRegression(view);

    view._width = width;
    view._height = height;
    view._svg = svg;
    view._canvas = canvas;
    view._xMap = xMap;
    view._yMap = yMap;
    view._zMap = zMap;
    view._yScale = yScale;
};

var plotLine = function (view) {

    // Get start and end point coordinates
    var x = window.app.filters.get(view.model.filter)._range;
    var y1 = view._yScale(view.model.alfa + x[0] * view.model.beta);
    var y2 = view._yScale(view.model.alfa + x[1] * view.model.beta);

    // Animate to the new postion, then immediately set stroke and color
    // This suppresses the (confusing) animation after a renderContent, but gives a slow delay
    view._svg.select(".regline").selectAll("line")
        .transition().duration(window.anim_speed)
            .attr("y1", y1)
            .attr("y2", y2)
        .transition().duration(0)
            .attr("stroke-width", 2)
            .attr("stroke", "black");
};

var plotPointsCanvas = function (view) {

    var opacity = 1.0, records;

    // Start with a clean plot
    view._canvas.clearRect(0, 0, view._width, view._height);
    var canvasData = view._canvas.getImageData(0, 0, view._width, view._height);

    // Modify canvas directly http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
    function drawPixel(x, y, color) {
        var index = (Math.round(x) + Math.round(y) * view._width) * 4;

        canvasData.data[index + 0] = color[0];
        canvasData.data[index + 1] = color[1];
        canvasData.data[index + 2] = color[2];
        canvasData.data[index + 3] = Math.round(color[3]*255*opacity);
    }

    function plotRecords() {
        var i = 0, cx, cy, cc;
        for(i=0; i < records.length; i++) {
            cx = view._xMap(records[i]) + margin.left;
            cy = view._yMap(records[i]) + margin.top;
            cc = view._zMap(records[i]);
            drawPixel(cx-1,cy  ,cc);
            drawPixel(cx+1,cy  ,cc);
            drawPixel(cx  ,cy-1,cc);
            drawPixel(cx  ,cy+1,cc);
            drawPixel(cx,  cy  ,cc);
        }
    }

    // remove our own filtering to also plot the points outside the filter
    if(view._dy) {
        view._dy.filterAll();
        opacity = 0.35;
    }
    else {
        opacity = 1.0;
    }

    // Plot all points (possibly ghosted)
    records = view._dy.top(Infinity);
    plotRecords();

    // Re-plot all selected points if we are filtering
    if(view.model.mode == 'select') {
        view._dy.filterFunction(view._filterFunction);
        records = view._dy.top(Infinity);

        opacity = 1.0;
        plotRecords();
    }

    // Write canvas to screen
    view._canvas.putImageData(canvasData, 0, 0);
};

var resetSelection = function (view) {

    // Remove previous selection
    if(view._dy) {
        view._dy.filterAll();
        view._dy.dispose();
        delete view._dy;
        delete view._filterFunction;
    }

    // Get fit parameters
    var xid = view.model.filter.toLowerCase();
    var yid = view.model.secondary.toLowerCase();
    var alfa = view.model.alfa;
    var beta = view.model.beta;
    var delta = Math.sqrt((1-view.model.R2)*view.model.vary) * view.model.count;

    var isInside;
    isInside = view.model.inout == "in" ? true : false;

    // Construct new filter
    view._dy = window.app.crossfilter.dimension(function(d) {
        var val = util.validateFloat(d[yid]) - (alfa + beta*util.validateFloat(d[xid]));
        return val;
    });
    view._filterFunction = function(d) {
        if (d > -delta && d < delta)
            return isInside;
        else
            return ! isInside;
    };
};

module.exports = ContentView.extend({
    template: templates.includes.correlation,

    bindings: {
        'model.pretty_fit': {
            type: 'text',
            hook: 'pretty-fit'
        },
    },

    cleanup: function () {
        if (this._dy) {
            this._dy.filterAll();
            this._dy.dispose();
        }
    },

    render: function() {
        var select;

        this.renderWithTemplate(this);

        // initialize secondary and color filter selector
        this.renderCollection(app.filters,
                              filterItemView,
                              this.queryByHook('filter-selector'),
                              {filter: function (f) {return f.active;}});
        select = this.el.querySelector('select[data-hook~="filter-selector"]');
        select.value = this.model.secondary;

        this.renderCollection(app.filters,
                              filterItemView,
                              this.queryByHook('color-selector'),
                              {filter: function (f) {return f.active;}});
        select = this.el.querySelector('select[data-hook~="color-selector"]');
        select.value = this.model.secondary;

        select = this.el.querySelector('[data-hook~="count"]');
        select.value = this.model.count;

        select = this.el.querySelector('[data-hook~="in-or-out"]');
        select.value = this.model.inout;

        select = this.el.querySelector('[data-hook~="do-fit"]');
        select.checked = (this.model.mode == "fit");

        select = this.el.querySelector('[data-hook~="do-select"]');
        select.checked = (this.model.mode == "select");

        return this;
    },

    renderContent: function (view) {
        if(! view.model.isReady) {
            return;
        }

        // Tear down old plot
        var el = view.queryByHook('scatter-plot');
        while (el.firstChild) {
            el.removeChild(el.firstChild);
        }
        if(view._svg) {
            delete view._svg;
        }
        if(view._canvas) {
            delete view._canvas;
        }

        // Set up and plot
        resetSelection(view);
        setupPlot(view);
        if(this.model.mode == 'select') {
            view._dy.filterFunction(view._filterFunction);
        }

        this.redraw();
    },

    // function called by dc on filter events.
    redraw: function () {
        if(! this.model.isReady) {
            return;
        }
        if(this.model.mode == 'fit') {
            // calculate linear regression coefficients:
            // y = alfa + beta * x
            var stats = this._reggroup.value();

            // update model
            var varx = (stats.xxsum / stats.count) - Math.pow(stats.xsum / stats.count,2);
            var vary = (stats.yysum / stats.count) - Math.pow(stats.ysum / stats.count,2);
            var covxy = (stats.xysum / stats.count) - (stats.xsum / stats.count) * (stats.ysum / stats.count);

            this.model.beta = covxy / varx;
            this.model.alfa = (stats.ysum / stats.count) - this.model.beta * (stats.xsum / stats.count);
            this.model.varx = varx;
            this.model.vary = vary;
            this.model.covxy = covxy;
            this.model.R2 = Math.pow(covxy, 2) / (varx * vary);
        }
        else if(this.model.mode == 'select') {
            // no-op, dont update the fit
        }

        plotPointsCanvas(this);
        plotLine(this);
    },

    // Respond to secondary filter changes
    events: {
        'change [data-hook~=filter-selector]': 'changeFilter',
        'change [data-hook~=color-selector]': 'changeColor',
        'change [data-hook~=count]': 'checkedCount',
        'change [data-hook~=in-or-out]': 'checkedInOrOut',
        'change [data-hook~=do-select]': 'checkedSelection',
        'change [data-hook~=do-fit]': 'checkedFit',
    },

    checkedFit: function () {
        var select = this.queryByHook('do-fit');
        if(! select.checked) return;

        this.model.mode = 'fit';
        resetSelection(this);
        this._dy.filterFunction(this._filterFunction);

        dc.redrawAll();
    },
    checkedSelection: function () {
        var select = this.queryByHook('do-select');
        if(! select.checked) return;

        this.model.mode = 'select';
        resetSelection(this);

        dc.redrawAll();
    },
    checkedInOrOut: function () {
        var select = this.el.querySelector('[data-hook~="in-or-out"]');
        this.model.inout = select.options[select.selectedIndex].value;

        resetSelection(this);
        this._dy.filterFunction(this._filterFunction);
        dc.redrawAll();
    },
    checkedCount: function () {
        var select = this.el.querySelector('[data-hook~="count"]');
        this.model.count = parseFloat(select.value);

        resetSelection(this);
        this._dy.filterFunction(this._filterFunction);
        dc.redrawAll();
    },

    changeFilter:  function () {
        var select = this.el.querySelector('[data-hook~="filter-selector"]');
        this.model.secondary = select.options[select.selectedIndex].value;

        this.renderContent(this);
    },
    changeColor: function() {
        var select = this.el.querySelector('[data-hook~="color-selector"]');
        this.model.color = select.options[select.selectedIndex].value;

        this.redraw();
    },
});
<commit_msg>Correlation: fix rendering of unselected points<commit_after>var app = require('ampersand-app');
var ContentView = require('./widget-content');
var filterItemView = require('./filteritem.js');
var templates = require('../templates');
var d3 = require('d3');
var util = require('../util');
var chroma = require('chroma-js');
var dc = require('dc');

var margin = {top: 10, bottom: 20, left: 30, right: 50}; // default margins from DC

var setupLinearRegression = function (view) {
    // Setup the map/reduce for the simple linear regression
    var xid = view.model.filter.toLowerCase();
    var yid = view.model.secondary.toLowerCase();

    var reduceAdd = function (p,v) {
        if( util.validateFloat(v[xid]) != Infinity && util.validateFloat(v[yid]) != Infinity ) {
            p.count++;
            p.xsum += v[xid];
            p.ysum += v[yid];
            p.xysum += v[xid] * v[yid];
            p.xxsum += v[xid] * v[xid];
            p.yysum += v[yid] * v[yid];
        }
        return p;
    };

    var reduceRemove = function (p,v) {
        if( util.validateFloat(v[xid]) != Infinity && util.validateFloat(v[yid]) != Infinity ) {
            p.count--;
            p.xsum -= v[xid];
            p.ysum -= v[yid];
            p.xysum -= v[xid] * v[yid];
            p.xxsum -= v[xid] * v[xid];
            p.yysum -= v[yid] * v[yid];
        }
        return p;
    };

    var reduceInitial = function () {
        return {
            count: 0,
            xsum: 0,
            ysum: 0,
            xysum: 0,
            xxsum: 0,
            yysum: 0,
        };
    };

    // Setup grouping
    var group = view._dy.groupAll();
    group.reduce(reduceAdd, reduceRemove, reduceInitial);

    return group;
};

var setupPlot = function (view) {
    var el = view.queryByHook('scatter-plot');
    var height = 250;
    var width = parseInt(el.offsetWidth);

    // add the graph to the body of the webpage
    var main = d3.select(el).append("div")
        .attr("width", width)
        .attr("height", height)
        .style('position', 'relative');

    var canvas = main.append("canvas")
        .attr("width", width)
        .attr("height", height)
        .node().getContext('2d');

    var svg = main.append("svg")
        .style("position", "absolute")
        .style("top", "0px")
        .style("left", "0px")
        .style("z-index", 2)
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var colorscale = chroma.scale(["#022A08", "#35FE57"]);

    var xrange = window.app.filters.get(view.model.filter)._range;
    var yrange = window.app.filters.get(view.model.secondary)._range;

    // Allow a no-color plot
    var zrange = [0,1];
    if(window.app.filters.get(view.model.color)) {
        zrange = window.app.filters.get(view.model.color)._range;
    }

    var xScale = d3.scale.linear().domain(xrange).range([0,width - margin.left - margin.right]);
    var yScale = d3.scale.linear().domain(yrange).range([height - margin.top - margin.bottom,0]);
    var zScale = d3.scale.linear().domain(zrange).range([0,1]);

    var xMap = function (d) {
        var v = util.validateFloat(d[view.model.filter.toLowerCase()]);
        if(isNaN(v) || v == Infinity) return -99999;
        return xScale(v);
    };
    var yMap = function (d) {
        var v = util.validateFloat(d[view.model.secondary.toLowerCase()]);
        if(isNaN(v) || v == Infinity) return -99999;
        return yScale(v);
    };
    var zMap = function (d) {
        var v = util.validateFloat(d[view.model.color.toLowerCase()]);
        if(isNaN(v) || v == Infinity) return chroma('gray').rgba();
        return colorscale(zScale(v)).rgba();
    };

    // x-axis
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + (height - margin.bottom - margin.top) + ")")
        .call(d3.svg.axis().scale(xScale).orient("bottom"))
        .append("text")
        .attr("class", "label")
        .attr("x", width - margin.left - margin.right)
        .attr("y", -6)
        .style("text-anchor", "end")
        .text(view.model.filter);

    // y-axis
    svg.append("g")
        .attr("class", "y axis")
        .call(d3.svg.axis().scale(yScale).orient("left"))
        .append("text")
        .attr("class", "label")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", ".71em")
        .style("text-anchor", "end")
        .text(view.model.secondary);

    // Fitted line
    svg.append("g")
        .attr("class", "regline")
        .append("line")
        .attr("x1", xScale(xrange[0]))
        .attr("x2", xScale(xrange[1]))
        .attr("y1", yScale(yrange[0]))
        .attr("y2", yScale(yrange[0]))
        .attr("stroke-width", 0)
        .attr("stroke", "black");

    if (view._reggroup) {
        view._reggroup.dispose();
    }
    view._reggroup = setupLinearRegression(view);

    view._width = width;
    view._height = height;
    view._svg = svg;
    view._canvas = canvas;
    view._xMap = xMap;
    view._yMap = yMap;
    view._zMap = zMap;
    view._yScale = yScale;
};

var plotLine = function (view) {

    // Get start and end point coordinates
    var x = window.app.filters.get(view.model.filter)._range;
    var y1 = view._yScale(view.model.alfa + x[0] * view.model.beta);
    var y2 = view._yScale(view.model.alfa + x[1] * view.model.beta);

    // Animate to the new postion, then immediately set stroke and color
    // This suppresses the (confusing) animation after a renderContent, but gives a slow delay
    view._svg.select(".regline").selectAll("line")
        .transition().duration(window.anim_speed)
            .attr("y1", y1)
            .attr("y2", y2)
        .transition().duration(0)
            .attr("stroke-width", 2)
            .attr("stroke", "black");
};

var plotPointsCanvas = function (view) {

    var opacity = 1.0, records;

    // Start with a clean plot
    view._canvas.clearRect(0, 0, view._width, view._height);
    var canvasData = view._canvas.getImageData(0, 0, view._width, view._height);

    // Modify canvas directly http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
    function drawPixel(x, y, color) {
        var index = (Math.round(x) + Math.round(y) * view._width) * 4;

        canvasData.data[index + 0] = color[0];
        canvasData.data[index + 1] = color[1];
        canvasData.data[index + 2] = color[2];
        canvasData.data[index + 3] = Math.round(color[3]*255*opacity);
    }

    function plotRecords() {
        var i = 0, cx, cy, cc;
        for(i=0; i < records.length; i++) {
            cx = view._xMap(records[i]) + margin.left;
            cy = view._yMap(records[i]) + margin.top;
            cc = view._zMap(records[i]);
            drawPixel(cx-1,cy  ,cc);
            drawPixel(cx+1,cy  ,cc);
            drawPixel(cx  ,cy-1,cc);
            drawPixel(cx  ,cy+1,cc);
            drawPixel(cx,  cy  ,cc);
        }
    }

    // remove our own filtering to also plot the points outside the filter
    if(view.model.mode == 'select') {
        view._dy.filterAll();
        opacity = 0.35;
    }
    else {
        opacity = 1.0;
    }

    // Plot all points (possibly ghosted)
    records = view._dy.top(Infinity);
    plotRecords();

    // Re-plot all selected points if we are filtering
    if(view.model.mode == 'select') {
        view._dy.filterFunction(view._filterFunction);
        records = view._dy.top(Infinity);

        opacity = 1.0;
        plotRecords();
    }

    // Write canvas to screen
    view._canvas.putImageData(canvasData, 0, 0);
};

var resetSelection = function (view) {

    // Remove previous selection
    if(view._dy) {
        view._dy.filterAll();
        view._dy.dispose();
        delete view._dy;
        delete view._filterFunction;
    }

    // Get fit parameters
    var xid = view.model.filter.toLowerCase();
    var yid = view.model.secondary.toLowerCase();
    var alfa = view.model.alfa;
    var beta = view.model.beta;
    var delta = Math.sqrt((1-view.model.R2)*view.model.vary) * view.model.count;

    var isInside;
    isInside = view.model.inout == "in" ? true : false;

    // Construct new filter
    view._dy = window.app.crossfilter.dimension(function(d) {
        var val = util.validateFloat(d[yid]) - (alfa + beta*util.validateFloat(d[xid]));
        return val;
    });
    view._filterFunction = function(d) {
        if (d > -delta && d < delta)
            return isInside;
        else
            return ! isInside;
    };
};

module.exports = ContentView.extend({
    template: templates.includes.correlation,

    bindings: {
        'model.pretty_fit': {
            type: 'text',
            hook: 'pretty-fit'
        },
    },

    cleanup: function () {
        if (this._dy) {
            this._dy.filterAll();
            this._dy.dispose();
        }
    },

    render: function() {
        var select;

        this.renderWithTemplate(this);

        // initialize secondary and color filter selector
        this.renderCollection(app.filters,
                              filterItemView,
                              this.queryByHook('filter-selector'),
                              {filter: function (f) {return f.active;}});
        select = this.el.querySelector('select[data-hook~="filter-selector"]');
        select.value = this.model.secondary;

        this.renderCollection(app.filters,
                              filterItemView,
                              this.queryByHook('color-selector'),
                              {filter: function (f) {return f.active;}});
        select = this.el.querySelector('select[data-hook~="color-selector"]');
        select.value = this.model.secondary;

        select = this.el.querySelector('[data-hook~="count"]');
        select.value = this.model.count;

        select = this.el.querySelector('[data-hook~="in-or-out"]');
        select.value = this.model.inout;

        select = this.el.querySelector('[data-hook~="do-fit"]');
        select.checked = (this.model.mode == "fit");

        select = this.el.querySelector('[data-hook~="do-select"]');
        select.checked = (this.model.mode == "select");

        return this;
    },

    renderContent: function (view) {
        if(! view.model.isReady) {
            return;
        }

        // Tear down old plot
        var el = view.queryByHook('scatter-plot');
        while (el.firstChild) {
            el.removeChild(el.firstChild);
        }
        if(view._svg) {
            delete view._svg;
        }
        if(view._canvas) {
            delete view._canvas;
        }

        // Set up and plot
        resetSelection(view);
        setupPlot(view);
        if(this.model.mode == 'select') {
            view._dy.filterFunction(view._filterFunction);
        }

        this.redraw();
    },

    // function called by dc on filter events.
    redraw: function () {
        if(! this.model.isReady) {
            return;
        }
        if(this.model.mode == 'fit') {
            // calculate linear regression coefficients:
            // y = alfa + beta * x
            var stats = this._reggroup.value();

            // update model
            var varx = (stats.xxsum / stats.count) - Math.pow(stats.xsum / stats.count,2);
            var vary = (stats.yysum / stats.count) - Math.pow(stats.ysum / stats.count,2);
            var covxy = (stats.xysum / stats.count) - (stats.xsum / stats.count) * (stats.ysum / stats.count);

            this.model.beta = covxy / varx;
            this.model.alfa = (stats.ysum / stats.count) - this.model.beta * (stats.xsum / stats.count);
            this.model.varx = varx;
            this.model.vary = vary;
            this.model.covxy = covxy;
            this.model.R2 = Math.pow(covxy, 2) / (varx * vary);
        }
        else if(this.model.mode == 'select') {
            // no-op, dont update the fit
        }

        plotPointsCanvas(this);
        plotLine(this);
    },

    // Respond to secondary filter changes
    events: {
        'change [data-hook~=filter-selector]': 'changeFilter',
        'change [data-hook~=color-selector]': 'changeColor',
        'change [data-hook~=count]': 'checkedCount',
        'change [data-hook~=in-or-out]': 'checkedInOrOut',
        'change [data-hook~=do-select]': 'checkedSelection',
        'change [data-hook~=do-fit]': 'checkedFit',
    },

    checkedFit: function () {
        var select = this.queryByHook('do-fit');
        if(! select.checked) return;

        this.model.mode = 'fit';
        resetSelection(this);
        this._dy.filterFunction(this._filterFunction);

        dc.redrawAll();
    },
    checkedSelection: function () {
        var select = this.queryByHook('do-select');
        if(! select.checked) return;

        this.model.mode = 'select';
        resetSelection(this);

        dc.redrawAll();
    },
    checkedInOrOut: function () {
        var select = this.el.querySelector('[data-hook~="in-or-out"]');
        this.model.inout = select.options[select.selectedIndex].value;

        resetSelection(this);
        this._dy.filterFunction(this._filterFunction);
        dc.redrawAll();
    },
    checkedCount: function () {
        var select = this.el.querySelector('[data-hook~="count"]');
        this.model.count = parseFloat(select.value);

        resetSelection(this);
        this._dy.filterFunction(this._filterFunction);
        dc.redrawAll();
    },

    changeFilter:  function () {
        var select = this.el.querySelector('[data-hook~="filter-selector"]');
        this.model.secondary = select.options[select.selectedIndex].value;

        this.renderContent(this);
    },
    changeColor: function() {
        var select = this.el.querySelector('[data-hook~="color-selector"]');
        this.model.color = select.options[select.selectedIndex].value;

        this.redraw();
    },
});
<|endoftext|>