/************************************************************* * test.js.php * * Main experiment file for the implicit memory test. * * Author: Trevor Croxson * Last Modified: November 5, 2015 * * © Copyright 2015 LabintheWild. * For questions about this file and permission to use * the code, contact us at info@labinthewild.org *************************************************************/ //Set Locale litw_locale = "en"; var urlSplit = window.location.href.split(/(\?|%3F)locale(=|%3D)/); if(urlSplit.length == 1){ // if no explicit locale set window.location.replace("?locale=" + litw_locale) } available_locales = { "en": {name: "English", direction: "ltr"}, "fa": {name: "فارسی", direction: "rtl", alternateNumerals: '۰۱۲۳۴۵۶۷۸۹'}, "ko": {name: "한국어",direction: "ltr"}, "zh": {name: "中文", direction: "ltr"} } window.direction = available_locales[litw_locale].direction; window.alternateNumerals = available_locales[litw_locale].alternateNumerals; function convertNumerals(strOrNum){ var str = strOrNum + ""; if(window.alternateNumerals){ return str.replace(/[0-9]/g, function(w){ return window.alternateNumerals[+w]; }); } return str; } function parseArabic(str) { return Number( str.replace(/[٠١٢٣٤٥٦٧٨٩]/g, function(d) { return d.charCodeAt(0) - 1632; // Convert Arabic numbers }).replace(/[۰۱۲۳۴۵۶۷۸۹]/g, function(d) { return d.charCodeAt(0) - 1776; // Convert Persian numbers }) ); } // load the average score and errors and languages for full RGB window.average_score = 0.918787; window.averageTileErrors = [0.321063, 0.160888, 0.386849, 0.446665, 0.39489, 0.20323, 0.161057, 0.163025, 0.240585, 0.148979, 0.231662, 0.493791, 0.171812, 0.518389, 0.366565, 0.194184, 0.453278, 0.442352, 0.141965, 0.26096, 0.186245, 0.330517, 0.336319, 0.260586, 0.330755, 0.476215, 0.23222, 0.149905, 0.413918, 0.491721, 0.252095, 0.311921, 0.164139, 0.395059, 0.118531, 0.440734, 0.237606, 0.0803556, 0.273881, 0.118158, 0.136652, 0.432454, 0.132987, 0.618175, 0.388171, 0.208871, 0.390267, 0.60396, 0.250411, 0.358993, 0.277264, 0.568448, 0.349896, 0.586679, 0.484929, 0.775237, 0.519309, 0.741186, 0.941617, 0.76961, 0.99335, 0.448155, 0.402448, 0.461454, 0.559993, 0.249361, 0.21772, 0.339459, 0.146365, 0.268579, 0.326452, 0.257654, 0.652612, 0.549982, 0.277311, 0.636895, 0.217293, 0.238415, 0.0917994, 0.104005, 0.331348, 0.100534, 0.370669, 0.390915, 0.29696, 0.117709, 0.222811, 0.427204, 0.277226, 0.164523, 'extra_value_hack_to_make_programming_easier']; window.averageTileErrors.pop(); window.languagesforfullrgb = ['Chinese (中文 (Zhōngwén), 汉语, 漢語)', 'Dutch (Nederlands, Vlaams)', 'English (English)', 'Finnish (suomi, suomen kieli)', 'French (français, langue française)', 'German (Deutsch)', 'Korean (한국어, 조선어)', 'Persian (Farsi) (فارسی)', 'Polish (język polski, polszczyzna)', 'Portuguese (português)', 'Romanian (limba română)', 'Russian (Русский)', 'Spanish (español)', 'Swedish (svenska)', 'extra_value_hack_to_make_programming_easier']; //re-process average tile errors // alias for the API var LE = LITWEngine; var studyVersion = "1.1.5"; var rgbSet = "line"; //LAB 120 /* var labRing = ["#fb99b7", "#fc99b4", "#fd99b0", "#fe99ac", "#fe99a8", "#fe99a5", "#fe9aa1", "#fe9a9d", "#fe9b9a", "#fd9b96", "#fd9c93", "#fc9d90", "#fb9e8c", "#f99f89", "#f8a086", "#f6a183", "#f5a281", "#f3a37e", "#f1a47b", "#eea679", "#eca777", "#eaa875", "#e7a973", "#e4ab71", "#e1ac70", "#dead6f", "#dbaf6e", "#d8b06d", "#d5b16c", "#d2b26c", "#ceb46b", "#cbb56b", "#c7b66b", "#c3b76c", "#c0b96d", "#bcba6d", "#b8bb6e", "#b4bc70", "#b0bd71", "#acbe73", "#a8bf75", "#a4c077", "#9fc179", "#9bc17b", "#97c27e", "#92c380", "#8ec483", "#89c486", "#85c589", "#80c68c", "#7bc690", "#77c793", "#72c797", "#6dc89a", "#68c89e", "#63c8a2", "#5ec9a5", "#58c9a9", "#53c9ad", "#4dc9b1", "#48cab5", "#42cab9", "#3ccabc", "#36cac0", "#2fcac4", "#29cac8", "#22cacc", "#1bcacf", "#13c9d3", "#0bc9d6", "#04c9da", "#00c8dd", "#00c8e0", "#03c8e3", "#0ac7e6", "#13c7e9", "#1bc6ec", "#23c6ee", "#2bc5f1", "#33c4f3", "#3ac3f5", "#42c3f7", "#49c2f9", "#50c1fa", "#57c0fc", "#5ebffd", "#65befe", "#6cbdfe", "#72bcff", "#79bbff", "#80baff", "#86b8ff", "#8cb7ff", "#92b6ff", "#99b5fe", "#9eb3fd", "#a4b2fc", "#aab1fb", "#afaff9", "#b5aef8", "#baacf6", "#bfabf4", "#c4aaf2", "#c9a8f0", "#cda7ed", "#d2a6eb", "#d6a4e8", "#daa3e5", "#dda2e2", "#e1a1df", "#e4a0dc", "#e89fd8", "#eb9ed5", "#ed9dd1", "#f09cce", "#f29bca", "#f49bc6", "#f69ac3", "#f89abf", "#fa99bb"].map(function(a){return d3.lab(d3.rgb(a))}); */ //LAB 90 var labRing = ["#fb99b7", "#fc99b2", "#fd99ad", "#fe99a8", "#fe9aa3", "#fe9a9f", "#fe9b9a", "#fd9c95", "#fc9d91", "#fb9e8c", "#f99f88", "#f7a184", "#f5a281", "#f2a47d", "#efa57a", "#eca777", "#e9a974", "#e5aa72", "#e1ac70", "#ddae6e", "#d9b06d", "#d5b16c", "#d0b36b", "#ccb56b", "#c7b66c", "#c2b86c", "#bdb96d", "#b8bb6e", "#b3bc70", "#adbe72", "#a8bf75", "#a2c077", "#9dc17a", "#97c27e", "#91c381", "#8bc485", "#85c589", "#7fc68e", "#78c792", "#72c797", "#6bc89c", "#65c8a0", "#5ec9a5", "#57c9aa", "#4fc9b0", "#48cab5", "#40caba", "#38cabf", "#2fcac4", "#27cac9", "#1dcace", "#13c9d3", "#08c9d7", "#01c9dc", "#00c8e0", "#05c8e4", "#10c7e8", "#1bc6ec", "#26c5ef", "#30c4f2", "#3ac4f5", "#44c2f8", "#4ec1fa", "#57c0fc", "#60bffd", "#6abdfe", "#73bcff", "#7bbaff", "#84b9ff", "#8cb7ff", "#95b5ff", "#9db4fe", "#a4b2fc", "#acb0fa", "#b3aef8", "#baacf6", "#c1abf3", "#c7a9f0", "#cda7ed", "#d3a5ea", "#d8a4e6", "#dea2e2", "#e2a0de", "#e79fd9", "#eb9ed5", "#ee9dd0", "#f29ccb", "#f49bc6", "#f79ac2", "#f999bd"] .map(function(a){return d3.lab(d3.rgb(a))}); var numberOfColorSets = 6; var colorsPerSet = labRing.length / numberOfColorSets; var colorSets = []; var resultsColorSets = [] var colorIndex = 0; for (var i = 0; i < numberOfColorSets; i++) { var colorSubset = []; var resultsColorSubset = []; //add color before color set var beforeIndex = colorIndex - 1; if(beforeIndex < 0){ beforeIndex += labRing.length; } colorSubset.push(labRing[beforeIndex]); resultsColorSubset.push(labRing[beforeIndex]); for(var j = 0; j < colorsPerSet; j++){ colorSubset.push(labRing[colorIndex]); resultsColorSubset.push(labRing[colorIndex]); colorIndex++; } //add color after var afterIndex = colorIndex; if(afterIndex >= labRing.length){ afterIndex -= labRing.length; } colorSubset.push(labRing[afterIndex]); resultsColorSubset.push(labRing[afterIndex]); colorSets.push(colorSubset); resultsColorSets.push(resultsColorSubset); }; var expData = { colorSets: colorSets, colorSetsShuffles: [ [12, 2, 14, 6, 9, 8, 1, 15, 7, 3, 10, 13, 5, 4, 11], [7, 11, 1, 10, 4, 15, 14, 6, 5, 2, 3, 13, 9, 8, 12], [3, 14, 7, 12, 1, 5, 2, 8, 15, 11, 10, 4, 13, 9, 6], [4, 11, 9, 1, 14, 10, 13, 2, 6, 5, 8, 15, 12, 3, 7], [13, 3, 8, 14, 2, 11, 4, 6, 7, 9, 15, 5, 1, 10, 12], [8, 9, 4, 5, 2, 13, 3, 7, 12, 11, 14, 6, 1, 15, 10] ], resultsColorSets: resultsColorSets, labRing: labRing, sortTilePage: 1, sortTileOrders: [], progression: ["name_tiles", "sort_tiles", "name_tiles", "break", "sort_tiles", "name_tiles"], trials: [ // first array is phase type; second is number of trials in the phase ["practice", "trial", "trial"], // note: rounding may produce a final trial count that is slightly different than the values below, depending on which values are used //[3, 3, 3] // 60% of original trial count: [5, 20, 60] ], commentsDone: false } function addShare() { share.linkedin = false; share.pinterest = false; share.sinaWeibo = true; share.makeButtons(".sharingButtons"); $('.sessionFlow img').width('22%'); } function setup() { LE.initialize(); addShare(); irb(); //expData.nativeLanguage = "English (English)" //nextPhase(); //results(); } function irb() { //in RTL languages, we mirror the next button box, so this is a hack to flip the inside back var rtlStyleHack1 = ""; if(window.direction == "rtl"){ rtlStyleHack1 = "style='float:right;'" } var html = "" + "

Welcome to the color perception study!

" + "

You're about to take a test on LabintheWild. Your contribution to our research allows us to learn more about cognitive differences around the world. The results from your test will also tell you something about yourself!

" + "

Please read the following information carefully before proceeding.

" + "Why we are doing this research: We are trying to understand how people perceive and name colors in different languages.

" + "

What you will have to do: You will be asked to do two different tasks. In one, you will be shown color tiles and be asked to enter the name of the color. In the other, you will be shown a line of color tiles and will be asked to sort them by hue.

" + "

What you will get out of it: We will give you feedback on how you performed on the tasks. The final results from this experiment will be posted on our blog page. The experiment is not explicitly designed to benefit you, but you may enjoy it and enjoy comparing your results with those of other participants.

" + "

Privacy and Data Collection: We will not ask you for your name. Any data that we collect will be securely stored on our servers.

" + "

Duration: Approximately 12 minutes.

" + "

Contact information: If you have questions about this research, you may contact Professor Katharina Reinecke, Paul G. Allen Center for Computer Science & Engineering, Box 352350, Seattle, WA 98195, reinecke@cs.washington.edu." + "

If you have questions about your rights as a research participant, or wish to obtain information, ask questions or discuss any concerns about this study with someone other than the researcher(s), please contact the University of Washington Human Subjects Division at 206-543-0098 (for international calls include the US Calling Code: +1-206-543-0098)." + "

" + "
"; $("#irb").html(html); $.each(available_locales, function(locale, localeInfo){ var opt = document.createElement("option"); opt.innerHTML = localeInfo.name; opt.value = locale; if(locale == litw_locale){ opt.selected = true; } $("#locale_select_irb").append(opt); }); $("body").css("direction", window.direction); if(window.direction == "rtl"){ $("#locale_select_irb").removeClass("pull-right"); $("#locale_select_irb").addClass("pull-left"); //Mirror the nextButton box //nevermind, informed it wasn't helpful to mirror it. //$("#nextButton").css("transform", "rotateY(180deg)"); } $("#locale_select_irb").change(function(){ var selectedLocale = $("#locale_select_irb").val(); if(selectedLocale != litw_locale){ window.location.replace("?locale=" + selectedLocale) } }); LE.showSlide("irb"); window.scrollTo(0, 0); //in RTL languages, we mirror the next button box, so this is a hack to flip the inside back var rtlStyleHack2 = ""; // nevermind, told it wasn't helpful to mirror next button //if(window.direction == "rtl"){ // rtlStyleHack2 = "style='transform:rotateY(180deg); float:left;'" //} $("#nextButton").html("
You must check the box to continue.
"); $("#agreeToStudy").on("click", function () { if ($("#agreeToStudy").is(':checked')) { LE.showNextButton(demographics); } else { $("#nextButton").html("
You must check the box to continue.
"); } }); } // variable to keep track of lighting info given before more iquestion is clicked var lighting = []; function updateVal(val, name) { var clampped = clamp(val); if(name == 'surrBright'){ lighting[0] = clampped; $("#surrBrightness")[0].value = clampped; $("#surrBrightnessSlider")[0].value = clampped; $("[for='surrBright'] span").html(clampped); } if(name == 'mBright'){ lighting[2] = clampped; $("#mBrightness")[0].value = clampped; $("#mBrightnessSlider")[0].value = clampped; $("[for='mBright'] span").html(clampped); } function clamp(value){ if (isNaN(value)) { return -1; }; if (value < 0){ return 0; } else if (value >100) { return 100; } else { return value; } } } function updateCheck(name, box){ if(name == "mBright"){ $("#mBrightIDK")[0].value = box.checked; } if(name == "surrBright"){ $("#surrBrightIDK")[0].value = box.checked; } } function demographics() { var languages = {}; languages["Other"] = "Other"; $.each(languages_iso_639, function(i, d){ languages[d["Language name"]] = d["Language name"] + " ("+d["Native name"]+")"; }); var languageFluencyOptions = { "fluently": "fluently", "very well": "very well", "well": "well", "at beginner's level": "at beginner's level" }; LE.newForm("demographics", { requiredQuestions: ["retake", "multinational", "country0", "gender", "age", "education", "multilingual", "lang0", "colorReading"] }) .add("retake") .add("multinational") .add("country0") .add("lang0", { name: "lang0", style: "dropdown", options: languages, prompt: "What is your native language? If more than one native language, choose the language you would prefer to name colors in.
", optionsAsNumbers: false, expand: "langAOtherText0", expansionTrigger: "Other" }) .add("langAOtherText0", { name: "langAOtherText0", style: "shortFreeText", prompt: "", expansionPrompt: "", expansionHeader: "Please fill in your native language", hidden: true, maxExpansions: 1 }) .add("lang1", { name: "lang1", style: "dropdown", options: languages, prompt: "List up to two other languages you know below:
    Language 2?", optionsAsNumbers: false, expand: "langBOtherText0", expansionTrigger: "Other" }) .add("langBOtherText0", { name: "langBOtherText0", style: "shortFreeText", prompt: "", expansionPrompt: "", expansionHeader: "Please fill in your second language", hidden: true, maxExpansions: 1 }) .add("fluency1", { name: "fluency1", style: "dropdown", options: languageFluencyOptions, prompt: "      Fluency in language 2?", }) .add("lang2", { name: "lang2", style: "dropdown", options: languages, prompt: "    Language 3?", optionsAsNumbers: false, expand: "langCOtherText0", expansionTrigger: "Other" }) .add("langCOtherText0", { name: "langCOtherText0", style: "shortFreeText", prompt: "", expansionPrompt: "", expansionHeader: "Please fill in your third language", hidden: true, maxExpansions: 1 }) .add("fluency2", { name: "fluency2", style: "dropdown", options: languageFluencyOptions, prompt: "      Fluency in language 3?", }) .add("gender") .add("age", {style: "numericalFreeText", prompt: "How old are you? (Please type a number between 6 and 99)" }) .add("education") .add("colorReading", { name: "colorReading", style: "dropdown", options: "yesNo", prompt: "Have you recently looked in detail at any website comparing how colors are named in different languages?" }) .add("colorWork", { name: "colorWork", style: "dropdown", options: "yesNo", prompt: "Do your work or studies require you to have a knowledge of different colors and their names that might be considered above average? (e.g. graphic designer, painter, make-up artist)?", expand: "colorWorkText0", expansionTrigger: "1", optionsAsNumbers: true }) .add("colorWorkText0", { name: "colorWorkText0", style: "freeText", prompt: "", expansionPrompt: "", expansionHeader: "Please tell us what those work or studies are (in a word or a short sentance)?", hidden: true, maxExpansions: 1 }) .add("colorBlindness", { name: "colorBlindness", style: "dropdown", options: {"none": "None", "red-green": "Red-green", "blue-yellow": "Blue-yellow", "total": "Total Color Blindness", "other": "Other"}, prompt: "If you have color vision deficiency, please select your type:", expand: "colorBlindnessText0", expansionTrigger: "other", optionsAsNumbers: false }) .add("colorBlindnessText0", { name: "colorBlindnessText0", style: "freeText", prompt: "", expansionPrompt: "", expansionHeader: "If you are comfortable, please describe your color blindness (in a word or a short sentance):", hidden: true, maxExpansions: 1 }) //brightness options .add("surrBrightIDK", { name: "surrBrightIDK", style: "freeText", prompt: "", hidden: true, maxExpansions: 1 }) .add("mBrightIDK", { name: "mBrightIDK", style: "freeText", prompt: "", hidden: true, maxExpansions: 1 }) .render(function(){ var lang_id = $("select#lang0").val(); if(lang_id == "Other"){ expData.nativeLanguage = $("#langAOtherText0").val(); }else{ expData.nativeLanguage = languages[lang_id]; } //remove whitespace, which is causing some problems in comparisons var languagesforfullrgb_no_white = window.languagesforfullrgb.map(function(s){return s.replace(/\s/g, '')}); if(languagesforfullrgb_no_white.indexOf(expData.nativeLanguage.replace(/\s/g, '')) >= 0){ //still collect line data 1 out of 10 times // otherwise collect from the full data set if(Math.floor(Math.random() * 10) != 0){ rgbSet = "full"; } } clearInterval(window.sortCountriesInterval); nextPhase(); }); //in RTL languages, we want the text to be to the right of the sliders var rtlStyleHack1 = ""; if(window.direction == "rtl"){ rtlStyleHack1 = "style='float:right;'" } //in LTR languages, we want the "don't know" text spaced over var rtlStyleHack2 = ""; if(window.direction == "ltr"){ rtlStyleHack2 = "col-sm-offset-4" } //in RTL languages, we want extra space after the "don't know" text var rtlStyleHack3 = ""; if(window.direction == "rtl"){ rtlStyleHack3 = "
" } //allow number input in alternate numeral set by hiding the "age" input // and creating another one whose input is then translated into arabic numerals in the "age" input $("#age").hide(); $("#ageContainer").append(""); $("#ageInput").change(function(){ var rawAge = $("#ageInput").val(); var age = parseArabic(rawAge); $("#age").val(age); }); var brightness_html = "
" + "" + "
" + "0 : " + 'Midnight pitch black' + "" + " 100 : " + 'Noon summer sun' + "" + "" + "
" + "" + "
" + "" + "
" + rtlStyleHack3 + "
" + "
" + "" + "
" + "0 : " + 'Minimum brightness' + "" + " 100 : " + 'Maximum brightness' + "" + "" + "
" + "" + "
" + " " + "
" + rtlStyleHack3 + "
"; $("#demographics form").append(brightness_html); $("#mBrightIDK")[0].value = false; $("#surrBrightIDK")[0].value = false; LE.showSlide("demographics"); window.scrollTo(0, 0); $('#lang0').addClass("js-states form-control lang-selection"); $('#lang0').select2({ placeholder: "click to search languages" }); $('#lang1').addClass("js-states form-control lang-selection"); $('#lang1').select2({ placeholder: "click to search languages" }); $('#lang2').addClass("js-states form-control lang-selection"); $('#lang2').select2({ placeholder: "click to search languages" }); //resort countries whenever a new country select appears (for non-english) window.sortCountriesInterval = setInterval(function(){ for(var i = 0; i < 10; i++){ var select_id = "#country" + i; if($(select_id)[0] && !$(select_id)[0].getAttribute("fixed")){ $(select_id)[0].setAttribute("fixed", "true");; //remove blank option blankOption = $(select_id)[0].children[0]; $(select_id)[0].remove($(select_id)[0].children[0]) //remove other contries country_options = [] while($(select_id)[0].children.length > 0){ country_options.push($(select_id)[0].children[0]); $(select_id)[0].remove($(select_id)[0].children[0]) } //sort country_options.sort(function(a,b) {return a.innerHTML.localeCompare(b.innerHTML)}); //re-insert $(select_id)[0].append(blankOption); for(var i = 0; i < country_options.length; i++){ $(select_id)[0].append(country_options[i]); } $(select_id)[0].children[0].selected = true; } } }, 100); } function getStepTitle(currentStep, totalSteps, title){ var title=""; if (currentStep === 1) { title = "Let’s start with some color naming!"; } else if (currentStep ===2) { title = "And now… let’s sort some colors!" }else if (currentStep ===3) { title = "More colors to name!"; }else if (currentStep ===4) { title = "Can you sort these colors too?"; }else if (currentStep ===5) { title = "Last step!" };; return "

" + title + " Step " + convertNumerals(currentStep) + " / " + convertNumerals(totalSteps) + "

"; } function name_tiles(currentStep, totalSteps){ LE.showSlide("name_tiles"); var html = getStepTitle(currentStep, totalSteps) + "

" + " Please enter the name of the color in " + expData.nativeLanguage + ". You can use color names that are as specific or as general as you want." + "

" + " If there is more than one character set for " + expData.nativeLanguage + ", please enter the names in the most common character set." + "

" + " Is " + expData.nativeLanguage + " not your language? You can restart the experiment to tell us about your actual first language by using this link." + "

" + " Once you have finished naming the colors, press the blue arrow to continue." + "

" + "
" $("#name_tiles").html(html); LE.showNextButton(function(){ submit_color_names(); nextPhase(); }, { "loadingOverride": true }); if(expData.color_naming_sets){ fill_in_name_tiles(); }else{ if(rgbSet == "line"){ loadLineColorNamingSet(fill_in_name_tiles); }else { getRandomFullRGBColors(fill_in_name_tiles); } } } function loadLineColorNamingSet(callback){ $.getJSON( "data/naming_color_set.json", function( data ) { var colorSet; var binEndPoints = []; var Nbin=36; var NScreens = 3; if(!expData.color_naming_sets){ colorSet = data; var endPoint = colorSet[colorSet.length-1].cummulative_dist + colorSet[colorSet.length-1].neighbor_dist; var bins = []; for(var i = 0; i < Nbin; i++){ bins.push(i); } shuffle(bins); expData.color_naming_sets = []; for(var i = 0; i < NScreens; i ++){ var color_naming_set = []; for(var j = 0; j < Nbin / NScreens; j++){ var binIndex = i * Nbin / NScreens + j; var color_dist = (bins[binIndex] + Math.random()) * endPoint / Nbin; color_naming_set.push(find_color_dist(colorSet, color_dist)); } expData.color_naming_sets.push(color_naming_set); } callback(); } }); } function find_color_dist(colorSet, colorDist){ //binary search to find color_dist var left = 0; var right = colorSet.length - 1; while(left <= right){ //chose midpoint (left) var mid = Math.floor((left + right) / 2); if(colorSet[mid].cummulative_dist <= colorDist && colorSet[mid].cummulative_dist + colorSet[mid].neighbor_dist >= colorDist){ return colorSet[mid]; } else if(colorSet[mid].cummulative_dist <= colorDist){ left = mid + 1; } else{ right = mid - 1; } } console.log("BINARY SEARCH FAILED"); } // choose a set of random rgb colors function getRandomFullRGBColors(callback){ var colorSet; var binEndPoints = []; var Nbin=36; var NScreens = 3; var allChosenColors = []; if(!expData.color_naming_sets){ expData.color_naming_sets = []; for(var i = 0; i < NScreens; i ++){ var color_naming_set = []; for(var j = 0; j < Nbin / NScreens; j++){ //pick a random color min_dist = 0; var newColor; do { newColor = getRandomRGBColor(); var min_dist = 100000; for(var k = 0; k < allChosenColors.length; k++){ var dist = labDist(newColor.lab, allChosenColors[k].lab); if(dist < min_dist){ min_dist = dist; } } } while(min_dist < 20); //while the current color is too similar to previously chosen colors // Note: just noticable difference in LAB is about 2.3 color_naming_set.push(newColor); allChosenColors.push(newColor); } expData.color_naming_sets.push(color_naming_set); } callback(); } } function labDist(lab1, lab2){ return Math.sqrt( Math.pow(lab1.l - lab2.l, 2) + Math.pow(lab1.a - lab2.a, 2) + Math.pow(lab1.b - lab2.b, 2)); } function labNeighborDist(color){ //find average distance in all 6 directions var dist = 0; var rgb = color.rgb; //note the math works even for rgb values past the limits (eg, -1, 256), so we don't worry about that. dist += labDist(color.lab, d3.lab(d3.rgb(rgb.r + 1, rgb.g, rgb.b))); dist += labDist(color.lab, d3.lab(d3.rgb(rgb.r - 1, rgb.g, rgb.b))); dist += labDist(color.lab, d3.lab(d3.rgb(rgb.r, rgb.g + 1, rgb.b))); dist += labDist(color.lab, d3.lab(d3.rgb(rgb.r, rgb.g - 1, rgb.b))); dist += labDist(color.lab, d3.lab(d3.rgb(rgb.r, rgb.g, rgb.b + 1))); dist += labDist(color.lab, d3.lab(d3.rgb(rgb.r, rgb.g, rgb.b - 1))); return dist / 6; } //choose a single random rgb color accounting for LAB space function getRandomRGBColor(){ var max_neighbor_dist = 0.7555604179499573 // max_color rgb = 27 23 24 //pick color, find neighborhood distance, find fractional dist, rand number 0-1, if rand too high, try again var foundColor = false; var color; while(!foundColor){ //pick random rgb values var r = Math.round(Math.random()*255); var g = Math.round(Math.random()*255); var b = Math.round(Math.random()*255); color = {}; color.rgb = d3.rgb(r, g, b); color.lab = d3.lab(color.rgb); //since rgb is not scaled perceptually, find the size of the LAB neighborhood of the color. // use this size to randomly skip perceptually smaller colors proportionally neighbor_dist = labNeighborDist(color); var dist_ratio = neighbor_dist/max_neighbor_dist; // decide if we get to keep the color based on the ratio and a random number; if(dist_ratio > Math.random()){ foundColor = true; } } return color; } function fill_in_name_tiles(){ var color_set = expData.color_naming_sets.shift(); $.each(color_set, function(index, color){ $('#naming-colors-set').append(create_color_tile_html(index, color.rgb)); }); $('input').on('keyup', name_tile_keyup); } function create_color_tile_html(index, color){ var $newColorColumn = $('
'); var $newColorPatch = $('
').css('background-color',d3.rgb(color.r, color.g, color.b).toString()); $newColorColumn.append($newColorPatch); $newColorColumn.append($("")); $newColorColumn.append($("
 
")); return $newColorColumn; } function name_tile_keyup(){ var text = $(this).val(); var korean = new RegExp("[\u1100-\u11FF|\u3130-\u318F|\uA960-\uA97F|\uAC00-\uD7AF|\uD7B0-\uD7FF]"); var chinese = new RegExp("[\u4E00-\u9FFF|\u2FF0-\u2FFF|\u31C0-\u31EF|\u3200-\u9FBF|\uF900-\uFAFF]"); var langDetector = { "Korean": korean, "Chinese": chinese }; var nativeLang = expData.nativeLanguage.split("(")[0].trim(); if(langDetector[nativeLang]) { if (!langDetector[nativeLang].test(text) && text !== "") { $(this).css('border-color',"#AA0000"); $(this).siblings('.help-block').html('Please write in '+ expData.nativeLanguage+'.'); } else{ $(this).css('border-color',"initial"); $(this).siblings('.help-block').html(' '); } } } function submit_color_names(){ var color_names = []; $('#naming-colors-set .color-column').each(function(){ var rgb_string = $(this).find('.color-tile').css("background-color"); var rgb = d3.rgb(rgb_string); var lab = d3.lab(rgb); var name = $(this).find('input').val(); color_names.push({ r: rgb.r, g: rgb.g, b: rgb.b, lab_l: lab.l, lab_a: lab.a, lab_b: lab.b, name: name}); }); var result = { participantId: LE.getParticipantId(), phaseNum: LE.getPhaseNum(), trialNum: LE.getTrialNum(), color_names: color_names, studyVersion: studyVersion, rgbSet: rgbSet, lang0: expData.nativeLanguage, locale: litw_locale }; LE.recordResult(result); $.ajax({ type: "POST", url: "include/name_data.php", data: result }).done(function(result) { //console.log(result); }); } function sort_tiles(currentStep, totalSteps){ LE.showSlide("sort_tiles"); var html = getStepTitle(currentStep, totalSteps) + "

" + " Sort the colors by hue so that similar colors are near each other. They should form a continuum between the two fixed color tiles at the ends of each row." + "

" + " You can either click and drag the tiles using a mouse or you can drag them with your finger on a touch screen." + "

" + " Once you have finished sorting the colors, press the blue arrow to continue." + "

" + "
" $("#sort_tiles").html(html); LE.showNextButton(function(){ submit_sort_tiles(); nextPhase(); }, { "loadingOverride": true }); fill_in_sort_tiles(); } function fill_in_sort_tiles(){ colorSets = []; var colorSets = []; for(var i = 0; i < 3; i++){ colorSets.push(expData.colorSets.shift()); $('#sorting-div').append('

'); } $('#sorting-div .sortable').each(function(index){ var $sortable = $(this); var $itemDiv = $('
'); var tempColorSet = colorSets.shift(); var colorSet = []; for(i = 0; i < tempColorSet.length; i++){ colorSet.push({ rgb: tempColorSet[i].rgb(), index: i }); } var width = 100 / (colorSet.length); $itemDiv.css('width', width+'%'); $sortable.append($itemDiv.clone().addClass('static').append($('
').css('background-color',colorSet.splice(0,1)[0].rgb.toString()))); var colorSetShuffle = expData.colorSetsShuffles.shift(); colorSet1Trimed = matchShuffle(colorSet.splice(0,colorSet.length-1), colorSetShuffle); for (var i = 0; i < colorSet1Trimed.length; i++) { $sortable.append( $itemDiv.clone().addClass('moveable').attr("patch_id", colorSet1Trimed[i].index) .append($('
').css('background-color',colorSet1Trimed[i].rgb.toString()))); }; $sortable.append($itemDiv.clone().addClass('static').css("margin-bottom","10px").append($('
').css('background-color',colorSet.splice(0,1)[0].rgb.toString()))); $sortable.data('drags',0); $sortable.data('sTime',0); $sortable.data('timerOn',false); $sortable.data('totalTime',0); $sortable.sortable({ revert: false, placeholder: "color-patch-placeholder", forcePlaceholderSize: false, tolerance: "pointer", items: '> div:not(.static)', start: function(){ var clickedTime = Date.now(); $sortable.data('drags', $sortable.data('drags') +1 ); if( !$sortable.data('timerOn') ){ $sortable.data('timerOn',true); $sortable.data('sTime', clickedTime); } $('.sortable').not($sortable).each(function(index, otherSortable){ if( $(otherSortable).data('timerOn') ){ var newTotalTime = $(otherSortable).data('totalTime') + clickedTime - $(otherSortable).data('sTime'); $(otherSortable).data('totalTime', newTotalTime); $(otherSortable).data('timerOn',false); } }) $('.color-patch-placeholder').css('width',width+'%'); $('.static', this).each(function(){ var $this = $(this); $this.data('pos', $( "div.color-patch", $sortable).index(this)); }); }, change: function(){ $sortable = $(this); $statics = $('.static', this).detach(); // $statics.each(function(){ // $('
').prependTo($sortable); // }) $('
').prependTo($sortable); $sortable.append($('
')); $statics.each(function(){ var $this = $(this); var target = $this.data('pos'); $this.insertAfter($('div.color-patch', $sortable).eq(target)); $('#sorting-div .static-helper').eq(0).remove(); }); } }); $sortable.disableSelection(); }); } function submit_sort_tiles(){ var sortingTilesOrder = []; var sortingTilesDrags = []; var sortingTilesTimes = []; $('#sorting-div .sortable').each(function(index){ var $sortable = $(this); var totalTime = $sortable.data('timerOn') ? Date.now() - $sortable.data('sTime') + $sortable.data('totalTime') : $sortable.data('totalTime'); var sortOrderRow = []; $sortable.find(".color-patch.moveable").each(function(index){ var $color_patch = $(this); var patch_id = parseInt($color_patch.attr("patch_id")); sortOrderRow.push(patch_id); }); sortingTilesOrder.push(sortOrderRow); sortingTilesDrags.push($sortable.data('drags')); sortingTilesTimes.push(totalTime); expData.sortTileOrders.push(sortOrderRow); }); var result = { participantId: LE.getParticipantId(), phaseNum: LE.getPhaseNum(), trialNum: LE.getTrialNum(), sortTilePage: expData.sortTilePage, sortTiles1: sortingTilesOrder[0].toString(), sortTiles2: sortingTilesOrder[1].toString(), sortTiles3: sortingTilesOrder[2].toString(), sortTiles1Drags: sortingTilesDrags[0], sortTiles2Drags: sortingTilesDrags[1], sortTiles3Drags: sortingTilesDrags[2], sortTiles1Time: sortingTilesTimes[0], sortTiles2Time: sortingTilesTimes[1], sortTiles3Time: sortingTilesTimes[2] }; LE.recordResult(result); $.ajax({ type: "POST", url: "include/sort_data.php", data: result }).done(function(result) { //console.log(result); }); expData.sortTilePage ++; } function shuffle(array) { var currentIndex = array.length, temporaryValue, randomIndex; // While there remain elements to shuffle... while (0 !== currentIndex) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } function matchShuffle(arrayToShufle, shuffleToMatch){ newArray = [] for(var i = 0; i < shuffleToMatch.length; i++){ newArray[shuffleToMatch[i] - 1] = arrayToShufle[i]; } return newArray; } function breakTime(){ LE.showSlide("break"); var html = "

" + "Nice work so far!

Feel free to take a break and rest your eyes before finishing the last two stages." + "

" + "
"; $("#break").html(html); LE.showNextButton(nextPhase, { "loadingOverride": true }); } function nextPhase() { var type = expData.progression.shift(); var totalSteps = 5; LE.increaseTrialCounter(); //progression: ["name_tiles", "sort_tiles", "name_tiles", "break", "sort_tiles", "name_tiles"], var numStepsLeft = expData.progression.length; var currentStep = totalSteps - numStepsLeft; //correct for break step which doesn't count if(currentStep < 3){ currentStep++; } if(type == "name_tiles"){ LE.recordProgression("name_tiles"); name_tiles(currentStep, totalSteps); } else if(type == "sort_tiles"){ LE.recordProgression("sort_tiles"); sort_tiles(currentStep, totalSteps); } else if (type == "break") { LE.recordProgression("mid-trial break"); breakTime(); } else { comments(); } window.scrollTo(0, 0); } function comments() { if (!expData.commentsDone) { LE.recordProgression("comments"); expData.commentsDone = true; LE.showCommentsPage(results); $('#comments h2.bolded-blue').text("Before we continue to your results..."); } else { results(); } } function results() { LE.recordProgression("results"); //var numberOfColorSets = 6; //var colorsPerSet = labRing.length var N = colorsPerSet // 15 (note in the actual set there are 17: two are the fixed start/end) var setN = numberOfColorSets // 6 userResponses = expData.sortTileOrders; //various fake datasets //var userResponses = []; /* // perfect data userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); */ // one mistake data /*userResponses.push([2,1,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); */ /* // specific mistakes data userResponses.push([2,1,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,9,8,10,11,12,13,14,15]); userResponses.push([8,2,3,4,5,6,7,1,9,10,11,12,13,14,15]); userResponses.push([15,2,3,4,5,6,7,8,9,10,11,12,13,14,1]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); */ /* // reversed data userResponses.push([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]); userResponses.push([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]); userResponses.push([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]); userResponses.push([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]); userResponses.push([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]); userResponses.push([15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]); */ //Good data /* userResponses.push([1,2,4,3,5,6,7,9,8,10,11,12,13,15,14]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,14,13,15]); userResponses.push([1,5,3,4,2,6,7,12,9,11,10,8,13,15,14]); userResponses.push([1,2,4,3,5,6,7,9,8,10,11,12,13,15,14]); userResponses.push([1,2,4,3,5,6,7,9,8,10,11,12,13,15,14]); userResponses.push([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]); */ /*//Random data userResponses.push([12, 8, 5, 6, 13, 4, 1, 2, 15, 7, 10, 3, 11, 14, 9]); userResponses.push([13, 1, 11, 14, 9, 2, 3, 12, 5, 7, 4, 8, 10, 15, 6]); userResponses.push([4, 2, 6, 3, 10, 5, 8, 12, 9, 15, 14, 7, 1, 13, 11]); userResponses.push([11, 4, 1, 15, 14, 9, 6, 2, 7, 8, 13, 10, 3, 5, 12]); userResponses.push([9, 14, 4, 8, 11, 1, 10, 6, 2, 15, 12, 13, 5, 3, 7]); userResponses.push([8, 15, 6, 5, 10, 7, 11, 3, 1, 13, 2, 9, 12, 14, 4]); */ var colorSets = expData.resultsColorSets; var results = colorSets.reduce(function(prev, colorSet, setIndex){ var splicedColorSet = colorSet.slice(1, colorSet.length - 1); prev = prev.concat(splicedColorSet.map(function(color, index, array){ return { "error" : Math.abs(index - userResponses[setIndex][index] + 1), "maxError": Math.max(index - 0, N - 1 - index), "color" : color, "index" : index + setIndex*N }; })); return prev; },[]); var avgUserResults = []; for(var i = 0; i < results.length; i++){ avgUserResults[i] = { "error" : window.averageTileErrors[i], "maxError": results[i].maxError, "color" : results[i].color, "index" : results[i].index } } // to compute max error for scores, reverse the list and see how bad it is (this should be close enough to correct) var maxError = 0; for(var i = 0; i < N; i++){ var currentPointError = Math.abs(i - (N - 1 - i)); maxError += Math.pow(currentPointError, 2); } maxError *= setN; // there are 6 sets var score = 1 - results.reduce(function(prev,curr){ prev += Math.pow(curr.error, 2); return prev; }, 0) / maxError; //to make scores (between 0 and 1) distributed better so not everyone scores 99+, // and to keep the range between 0 and 1 and to particularly separate high scores // do basically (1 - sqrt(1 - score)), but with a slighlty different exponent so one error rounds as 99 score = 1 - Math.pow(1 - score, .55); // make all scores less than 35.2 be 0 score = Math.max(0, (score - 0.352) / (1 - 0.352)); var roundedScore = Math.round(score*100); var roundedAvgScore = Math.round(window.average_score*100); var bannerText = ""; if(roundedScore > 60){ bannerText = "Good job!"; } LE.showSlide("results"); var html = '
' + '

Your color perception score is ...

' + '
' + '
' + '
' + '
' + '
' + ' ???' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '

' + ' '+bannerText+'' + '

' + '
' + "

" + "

" + "

" + "

" + "

" + " Your score indicates how close your tiles were to where they should go if they are sorted with 100% accuracy." + "

" + "
" + "

" + " Color Perception on the Spectrum" + "

" + "

" + " Which colors could you discriminate well or poorly?" + "

" + "

" + " On the spectrum below you will see a chart showing how well you were at discriminating different colors." + "

" + "

" + " Where the line is higher, you placed colors closer to their correct position. " + "

" + "

" + " Where the line is lower, you placed colors further away from their correct position." + "

" + "
" + "
Your Spectrum
" + "
" + "
" + "

" + " How did the average user do?" + "

" + "

" + " On the spectrum below you will see a chart showing how well the average user was at discriminating different colors." + "

" + "
" + "
Average User Spectrum
" + "
" + "
" + "
" + "

Color Names" + "

" + "

" + " Thank you for naming colors for us! Those names are not part of your color perception score. Instead, we will use them as part of a research study on how colors are named in different languages, which is why we could not give you examples color names or hints." + "

" + //"

" + //" If you would like to see the results from the color naming task, enter your email below and we will email you when the study is finished. Your email address will be kept separately from the data you entered." + //"

" + "

" + " See our latest results at" + ": https://uwdata.github.io/color-naming-in-different-languages" + "

" + //"
" + //"
" + //" Email address: " + //" " + //"
" + //"
" + "
Share your results! Language of link: " + " " + "
" + "" + "
" $("#results").html(html); share.makeButtons(".sharingButtons"); $('.sessionFlow img').width('22%'); $('.sessionFlow img').width('22%'); $("#sharelinkurl").val(window.location); $("#sharelinkbutton").click(function(){ $("#sharelinkurl").select(); document.execCommand("copy"); }); $.each(available_locales, function(locale, localeInfo){ var opt = document.createElement("option"); opt.innerHTML = localeInfo.name; opt.value = locale; if(locale == litw_locale){ opt.selected = true; } $("#locale_select_share").append(opt); }); //set locale of link to current language $("#resultsShare").children().each(function(i, shareLink){ var url = shareLink.href; var urlStart = url.split(/(\?|%3F)locale(=|%3D)/)[0]; shareLink.href = urlStart + "%3Flocale%3D" + litw_locale; }); //change locale of link $("#locale_select_share").change(function(){ var selectedLocale = $("#locale_select_share").val(); $("#resultsShare").children().each(function(i, shareLink){ var url = shareLink.href; var urlStart = url.split(/(%2F|\/)(%3F|\?)/)[0]; shareLink.href = urlStart + "%2F%3Flocale%3D" + selectedLocale; }); var windowurl = window.location.href; var urlStart = windowurl.split(/(%2F|\/)(%3F|\?)/)[0]; $("#sharelinkurl").val(urlStart + "/?locale=" + selectedLocale); }); $("#resultsPageCommentsBtn").on("click", function() { var comments = $("#resultsPageComments").val(); $.ajax({ type: "POST", url: "include/resultsPageComments.php", data: { participantId: LE.getParticipantId(), comments: comments } }).done(function() { $("#resultsPageComments").val("Thanks for the feedback!").css("color", "#AAAAAA").prop("disabled", true); }); $(this).prop("disabled", true); }); LE.showFooter(["How good is your implicit memory?", "What is your thinking style?"], "resultsFooter"); LE.hideNextButton(); //callback handler for email-list-form submit $("#email-list-form").submit(function(e) { var postData = $(this).serializeArray(); var formURL = $(this).attr("action"); $.ajax( { url : formURL, type: "POST", data : postData, success:function(data, textStatus, jqXHR) { //data: return data from server if(data == ""){ $("#email-list-form-div").append("Email submitted
"); }else{ $("#email-list-form-div").append("error submitting email
"); } }, error: function(jqXHR, textStatus, errorThrown) { //if fails $("#email-list-form-div").append("error submitting email
"); } }); e.preventDefault(); //STOP default action }); //$("#ajaxform").submit(); //Submit the FORM drawSpectrumGraph("spectrum", results, N, setN); drawSpectrumGraph("spectrum2", avgUserResults, N, setN); $('#score').html(convertNumerals(roundedScore)); if(window.average_score + .01 < score){ $('#score-compare').html('Your score of '+convertNumerals(roundedScore)+' out of 100 was higher than the average score of '+convertNumerals(roundedAvgScore)+'.'); } else if(window.average_score - .01 > score){ $('#score-compare').html('Your score of '+convertNumerals(roundedScore)+' out of 100 was lower than the average score of '+convertNumerals(roundedAvgScore)+'.'); } else { $('#score-compare').html('Your score of '+convertNumerals(roundedScore)+' out of 100 was about the same as the average score of '+convertNumerals(roundedAvgScore)+'.'); } //submit score to DB var result = { participantId: LE.getParticipantId(), score: score }; LE.recordResult(result); $.ajax({ type: "POST", url: "include/score_data.php", data: result }).done(function(result) { //console.log(result); }); } function drawSpectrumGraph(domId, data, N, setN){ var margin = {"top": 20, "bottom": 50, "left": 0, "right": 40}; var width = $('#score-medal').width(), height= 130; if(width > 800){ width = 800; } var svg = d3.select("#" + domId).append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var x = d3.scale.linear() .rangeRound([0, width]) .domain([0,N*setN-1]); var y = d3.scale.linear() .rangeRound([0, height]) .domain([0, 1]); var colors = svg.selectAll('.color') .data(data) .enter() .append('g') .attr('class','color'); var colorPatches = colors.append('rect') .attr('x', function(d){ return x(d.index); }) .attr('y', 0 ) .attr('width', function(d){ return x(d.index+1)-x(d.index) + .5;}) .attr('height', height ) .attr('fill', function(d){ return d.color.rgb(); }); var intervals = colors.append('line') .filter(function(d){ return d.index%N===0 && d.index!==0;}) .attr('x1', function(d){ return x(d.index);}) .attr('x2', function(d){ return x(d.index);}) .attr('y1', function(d){ return 0;}) .attr('y2', function(d){ return height;}) .attr('fill','none') .attr('stroke','black') .attr('opacity',0.18) .attr('stroke-width', 2); var line = d3.svg.line() .x(function(d){ return (x(d.index+1)+x(d.index))/2; }) .y(function(d){ return y(d.error / d.maxError) + 2; }) .interpolate('basis'); var line2 = d3.svg.line() .x(function(d){ return (x(d.index+1)+x(d.index))/2; }) .y(function(d){ return y(d.error / d.maxError)+ 3.5; }) .interpolate('basis'); svg.append('path') .attr("d", line(data)) .attr("stroke", "black") .attr("stroke-width", 2) .attr("fill", "none"); svg.append('path') .attr("d", line2(data)) .attr("stroke", "white") .attr("stroke-width", 2) .attr("fill", "none"); // var textXPos = 0; // text in RTL languages (like Farsi) needs to start on the right side of the graph if(window.direction == "rtl"){ textXPos = width + margin.left + margin.right; } svg.append('text') .attr("x", textXPos ) .attr("y", 15 - margin.top) .text("Tile in correct position") svg.append('text') .attr("x", textXPos ) .attr("y", height + 15) .text("Tile far from correct position") svg.append('text') .attr("x", textXPos ) .attr("y", height + 30) .attr("fill", "#999") .text("*The spectrum is divided into the six color sets you sorted.") } $(document).ready(setup);