| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <style> | |
| div.main{ | |
| min-width: 970px; | |
| padding: 0; | |
| margin: 0; | |
| } | |
| div.left { | |
| vertical-align: top; | |
| display: inline-block; | |
| width: 645px; | |
| height: 720px; | |
| padding: 1.25em; | |
| } | |
| div.right { | |
| vertical-align: top; | |
| display: inline-block; | |
| width: 200px; | |
| height: 720px; | |
| padding: 1.25em; | |
| } | |
| div.right1, div.right2 { | |
| height: 300px; | |
| padding: 0px; | |
| margin: 0px; | |
| } | |
| footer { | |
| font-size: 12px; | |
| padding: 1.25em; | |
| margin-left: 33px; | |
| } | |
| div.answer { | |
| position: relative; | |
| left: 0px; | |
| height: 600px; | |
| text-align: center; | |
| margin-top: 20px; | |
| margin-left: 20px; | |
| } | |
| div.question, div.feedback { | |
| font-size: 36px; | |
| text-align: center; | |
| margin-bottom: 5px; | |
| background-color: white; | |
| /* outer shadows (note the rgba is red, green, blue, alpha) */ | |
| -webkit-box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.4); | |
| -moz-box-shadow: 0px 1px 6px rgba(23, 69, 88, .5); | |
| /* rounded corners */ | |
| -webkit-border-radius: 12px; | |
| -moz-border-radius: 7px; | |
| border-radius: 7px; | |
| /* gradients */ | |
| background: -webkit-gradient(linear, left top, left bottom, | |
| color-stop(0%, white), color-stop(15%, white), color-stop(100%, #D7E9F5)); | |
| background: -moz-linear-gradient(top, white 0%, white 55%, #D5E4F3 130%); | |
| } | |
| div.result, div.menu { | |
| font-size: 16px; | |
| font-weight: bold; | |
| text-align: center; | |
| } | |
| div.resultsCorrect { | |
| background-color: #5ab4ac; | |
| background-color: #a1d99b; | |
| } | |
| div.resultsIncorrect { | |
| background-color: #fee0d2; | |
| } | |
| circle { | |
| fill-opacity: 0.7; | |
| stroke: black; | |
| } | |
| .line { | |
| stroke: grey; | |
| } | |
| text.number { | |
| font-size: 17px; | |
| } | |
| .number { | |
| pointer-events: none; | |
| } | |
| </style> | |
| <script src="http://d3js.org/d3.v3.js"></script> | |
| </head> | |
| <body> | |
| <div class="main"> | |
| <div class="left"> | |
| <div class="question"></div> | |
| <div class="feedback"></div> | |
| <div class="answer"></div> | |
| </div> | |
| <div class="right"> | |
| <div class="right1"> | |
| <div class="menu"></div> | |
| <p> | |
| <select id ="sp" name="language" onchange="setLanguage(this.value, true);"> | |
| <option value="english" selected>English</option> | |
| <option value="french">Français</option> | |
| <option value="spanish">Español</option> | |
| <option value="german">Deutsch</option> | |
| <option value="hebrew">עברי</option> | |
| <option value="arabic">العربية</option> | |
| </select> | |
| </p> | |
| <p> | |
| <select id ="ro" name="operations" onchange="reset()"> | |
| <option value="+" selected>+ Addition</option> | |
| <option value="-">- Subtraktion</option> | |
| <option value="*">\xB7 Multiplikation</option> | |
| <option value=":">: Division</option> | |
| </select> | |
| </p> | |
| <p> | |
| <input type="checkbox" id="cbox1" value="first_checkbox" onchange="checkTime()"><label class="time">Zeitmessung (10 Fragen)</label> | |
| </p> | |
| <p> | |
| <input checked="true" type="checkbox" id="cbox2" value="second_checkbox"><label class="sound">Ton</label> | |
| </p> | |
| </div> | |
| <div class="right2"> | |
| <div class="result"></div> | |
| <p class="timer"></p> | |
| <p class="rightAnswers"></p> | |
| <p class="wrongAnswers"></p> | |
| </div> | |
| </div> | |
| </div> | |
| <footer> | |
| <p>©2016 ee2.dev@gmail.com - page best viewed with Google Chrome. </p> | |
| </footer> | |
| <script> | |
| 'use strict' | |
| // evaluate answers | |
| var totalCorrect = 0; | |
| var totalQuestions = 0; | |
| var correctAnswers = []; | |
| var incorrectAnswers = []; | |
| var data; // data: results for forced layout | |
| var value1, value2, result; // values for question + solution | |
| // translate Object for language specific text | |
| // properties: calcOptions, timeString, positiveComment, negativeComment, infoComment, resultComment, | |
| // soundLabel, correctResults, incorrectResults, timeResults; | |
| var translate = {}; | |
| var counter = 0; | |
| var myTimer; | |
| var selectedIndex; | |
| var indexOf; // d3.map. map value -> index of node | |
| var nodes; | |
| var links; | |
| var nodesRemoved; | |
| var linksRemoved; | |
| var force = d3.layout.force(); | |
| var chargeValue = -220; | |
| // variables for the SVG | |
| var svgHeight = 600; | |
| var svgWidth = 600; | |
| var fontSize = 20; | |
| var radius = 15, cx = 0, cy = 0; | |
| var colors = d3.scale.category10(); | |
| // to distinguish between drag and click | |
| // Workaround because of fired mousemove events https://bugs.chromium.org/p/chromium/issues/detail?id=161464 | |
| var mousemove = 0; | |
| var dragFlag = false; | |
| var svg = d3.select("div.answer") | |
| .append("svg") | |
| .attr("style", "outline: thin solid steelblue;") | |
| .attr("height", svgHeight) | |
| .attr("width", svgWidth); | |
| reset(); | |
| function reset() { | |
| var questionType = d3.select("select#ro").node().value; | |
| newQuestion(questionType); | |
| draw(data); | |
| setLanguage(d3.select("select#sp").node().value, true); | |
| checkTime(); | |
| } | |
| function newQuestion(questionType, numberRange) { | |
| var maxValue; | |
| d3.select("div.question") | |
| .style("background", "rgba(0, 0, 0, 0) -webkit-gradient(linear, 0% 0%, 0% 100%, from(white), color-stop(0.15, white), to(rgb(215, 233, 245))) repeat scroll 0% 0% / auto padding-box border-box"); | |
| if (!questionType) { questionType = d3.select("#ro").node().value;} | |
| switch (questionType) { | |
| case "+": | |
| data = d3.range(0, 100, 1); | |
| result = getRandomInteger(data[0],data[data.length-1]); | |
| value2 = getRandomInteger(data[0], result); | |
| value1 = result - value2; | |
| break; | |
| case "-": | |
| data = d3.range(0, 100, 1); | |
| value1 = getRandomInteger(data[0],data[data.length-1]); | |
| value2 = getRandomInteger(data[0], value1); | |
| result = value1 - value2; | |
| break; | |
| case "*": | |
| data = d3.range(0, 10, 1); | |
| value1 = getRandomInteger(data[0],data[data.length-1]); | |
| value2 = getRandomInteger(data[0],data[data.length-1]); | |
| result = value1 * value2; | |
| data = d3.range(0, 100, 1); | |
| break; | |
| case ":": | |
| data = d3.range(0, 10, 1); | |
| result = getRandomInteger(data[0],data[data.length-1]); | |
| value2 = getRandomInteger(data[1],data[data.length-1]); | |
| value1 = value2 * result; | |
| break; | |
| } | |
| questionType = (questionType === "*") ? "\xB7" : questionType; | |
| d3.select("div.question") | |
| .text(value1 + " " + questionType + " " + value2 + " = "); | |
| } | |
| // functions for drawing the SVG of the answer div | |
| function draw(_data) { | |
| nodes = []; | |
| links = []; | |
| selectedIndex = -1; | |
| nodesRemoved = []; | |
| linksRemoved = []; | |
| svg.selectAll("g.node-objects").remove(); | |
| createNodesAndLinks(_data); | |
| setupForceLayout(); | |
| drawNodes(); | |
| } | |
| function createNodesAndLinks(_data) { | |
| var startingPoint = [350, 700]; | |
| indexOf = d3.map(); | |
| for (var i = 0; i < _data.length; ++i) { | |
| nodes.push({ | |
| x: startingPoint[0] + Math.random() * 100, | |
| y: startingPoint[1] + Math.random() * 100, | |
| value: _data[i] | |
| }); | |
| indexOf.set(nodes[nodes.length-1].value, i); | |
| if (i % 10 !== 0) { // add links to predecessor for nodes except for 0,10,20,... | |
| links.push({source: nodes[i-1], target: nodes[i]}); | |
| if (i % 10 === 9) { // add links to create circular graph for 10's, 20s,.. | |
| links.push({source: nodes[i-9], target: nodes[i]}); | |
| } | |
| } | |
| } | |
| } | |
| function setupForceLayout() { | |
| force.nodes(nodes) | |
| .size([svgWidth, svgHeight]) | |
| .links(links) | |
| .gravity(0.1) | |
| .charge( function(d) { return (d.index === selectedIndex) ? chargeValue : -80;}) | |
| .friction(0.95) | |
| .linkDistance(30) | |
| .linkStrength( function(d) { return (d.index === selectedIndex) ? 0 : 1;}); | |
| force.on("tick", function () { | |
| svg.selectAll("g.node-objects") | |
| .filter( function(d) { return nodesRemoved.indexOf(d.index ) === -1; }) | |
| .attr("transform", function (d) {return "translate (" + d.x + ", " + d.y +")";}); | |
| }); | |
| force.start(); | |
| } | |
| function drawNodes() { | |
| var nodeObjects = svg.selectAll("g.node-objects") | |
| .data(nodes) | |
| .enter() | |
| .append("g") | |
| .attr("class", "node-objects") | |
| .attr("transform", "translate (-50, -50)") // invisible start position | |
| .on("mousedown", function (d) { mousemove = 0; dragFlag = false; }) | |
| .on("mousemove", function (d) { mousemove++; if (mousemove > 1) dragFlag = true; }) | |
| .on("mouseup", function (d) { | |
| if (dragFlag) {return;} | |
| var transX = d3.transform(d3.select(this).attr("transform")).translate[0]; | |
| var transY = d3.transform(d3.select(this).attr("transform")).translate[1]; | |
| playSound("blow.mp3"); | |
| d3.select(this) | |
| .each(function(d) { | |
| selectedIndex = indexOf.get(d.value); | |
| nodes[selectedIndex].fixed = true; | |
| }) | |
| .transition() | |
| .duration(1000) | |
| .ease("circle") | |
| .attr("transform", "translate(" + transX + ", " + transY +") scale(3)") | |
| .each("end", evaluate); | |
| // detach links from selected node by self reference. | |
| // Otherwise index shifts would have to be taken care of | |
| if (selectedIndex === 0) { | |
| force.links()[selectedIndex] = {source: nodes[selectedIndex], target: nodes[selectedIndex]}; | |
| } | |
| else { | |
| force.links()[selectedIndex-1] = {source: nodes[selectedIndex-1], target: nodes[selectedIndex-1]}; | |
| force.links()[selectedIndex] = {source: nodes[selectedIndex], target: nodes[selectedIndex]}; | |
| } | |
| nodesRemoved.push(selectedIndex); | |
| force.start(); // update charge for selected node | |
| }) | |
| .call(force.drag); | |
| nodeObjects.append("circle") | |
| .attr("class", "node") | |
| .attr("r", radius) | |
| .style("fill", function (d, i) { return colors(Math.ceil((i+1)/10));}); | |
| nodeObjects.append("text") | |
| .attr("class", "number") | |
| .attr("x", function(d) { | |
| if (d.value > 99) { | |
| return - fontSize*0.7; | |
| } | |
| else if (d.value > 9) { | |
| return - fontSize*0.5; | |
| } | |
| else { | |
| return - fontSize * 0.25; | |
| }}) | |
| .attr("y", function(d) { return - fontSize * 0.35;}) | |
| .attr("dy", ".71em") | |
| .text(function(d, i) { return d.value;}); | |
| } | |
| function evaluate(d, i) { | |
| var guess = d.index; | |
| totalQuestions++; | |
| if (totalQuestions === 10) { | |
| clearInterval(myTimer); | |
| } | |
| var feedback = d3.select("div.feedback") | |
| feedback.select("h1").remove(); | |
| if (guess === result) { | |
| totalCorrect++; | |
| correctAnswers.push(d3.select("div.question").text() + guess); | |
| d3.select("div.question") | |
| .style("background", "none") | |
| .style("background-color", "lightgreen") | |
| .text(correctAnswers[correctAnswers.length-1]); | |
| showSuccess(); | |
| feedback.html(function() { return translate.positiveComment[getRandomInteger(0, translate.positiveComment.length-1)]}) | |
| .transition() | |
| .duration(2000) | |
| .each("end", newQuestion); | |
| d3.select("div.result").text(translate.resultComment + ": " + totalCorrect + "/" + totalQuestions); | |
| } | |
| else { | |
| incorrectAnswers.push(d3.select("div.question").text() + guess); | |
| feedback.html(function() { return translate.negativeComment[getRandomInteger(0, translate.negativeComment.length-1)]}); | |
| burstCircle(); | |
| d3.select("div.result").text(translate.resultComment + ": " + totalCorrect + "/" + totalQuestions); | |
| } | |
| showResults(); | |
| } | |
| function burstCircle() { | |
| console.log("fail"); | |
| playSound("burst.mp3"); | |
| var selectedCircle = d3.selectAll("circle") | |
| .filter (function(d){return d.index === selectedIndex;}); | |
| selectedCircle.transition() | |
| .duration(100) | |
| .style("fill-opacity", 0) | |
| .style("stroke", "none") | |
| .each("end", dropNumber); | |
| force.stop(); | |
| chargeValue = -10000; | |
| force.start(); | |
| } | |
| function dropNumber() { | |
| force.stop(); | |
| chargeValue = -220; | |
| force.start(); | |
| var yPos = force.nodes()[selectedIndex].y; | |
| d3.selectAll("circle.node") | |
| .filter (function(d){return d.index === selectedIndex;}) | |
| .remove(); | |
| var selectedText = d3.selectAll("text.number") | |
| .filter (function(d){return d.index === selectedIndex;}) | |
| selectedText.transition() | |
| .duration(2000) | |
| .ease("bounce") | |
| .attr("y", (svgHeight - fontSize*0.35 - yPos)/3 - 10); | |
| } | |
| function showSuccess() { | |
| console.log("success"); | |
| playSound("cheer.mp3"); | |
| force.stop(); | |
| var circles = d3.selectAll("circle.node") | |
| .filter (function(d){return d.index !== selectedIndex;}); | |
| circles.transition() | |
| .duration(1000) | |
| .style("fill-opacity", 0) | |
| .style("stroke-opacity", 0.1); | |
| var numbers = d3.selectAll("text.number") | |
| .filter (function(d){return d.index !== selectedIndex;}); | |
| numbers.transition() | |
| .duration(2000) | |
| .style("opacity", 0.1) | |
| .filter(function(d, i) { return (i === data.length - 2) ? true : false; }) | |
| .each("end", function(){ draw(data);}); | |
| } | |
| function showResults() { | |
| if (correctAnswers.length > 0){ | |
| d3.select("p.rightAnswers") | |
| .text(translate.correctResults) | |
| .selectAll("div.resultsCorrect") | |
| .data(correctAnswers) | |
| .enter() | |
| .append("div") | |
| .attr("class", "resultsCorrect") | |
| .text(function(d) {return d;}); | |
| } | |
| if (incorrectAnswers.length > 0){ | |
| d3.select("p.wrongAnswers") | |
| .text(translate.incorrectResults) | |
| .selectAll("div.resultsIncorrect") | |
| .data(incorrectAnswers) | |
| .enter() | |
| .append("div") | |
| .attr("class", "resultsIncorrect") | |
| .text(function(d) {return d;}); | |
| } | |
| } | |
| function playSound(soundfile) { | |
| if (document.getElementById("cbox2").checked) { | |
| var mp3 = new Audio(soundfile); | |
| mp3.play(); | |
| } | |
| } | |
| // function 1 for the timer | |
| function checkTime() { | |
| if (document.getElementById("cbox1").checked===false) { | |
| clearInterval(myTimer); | |
| d3.select("p.timer").text(""); | |
| d3.select("div.timer").remove(); | |
| } else { | |
| counter = 0; | |
| d3.select("p.timer") | |
| .text(translate.timeResults) | |
| .append("div") | |
| .attr("class", "timer"); | |
| myTimer = setInterval(countNow, 10); | |
| } | |
| } | |
| // function 2 for the timer | |
| function countNow() { | |
| counter += 0.01; | |
| d3.select("div.timer").text(counter.toFixed(2)); | |
| } | |
| function getRandomInteger(lower, upper) { | |
| if (arguments.length === 1) { | |
| upper = lower; | |
| lower = 0; | |
| } | |
| return Math.floor(Math.random() * (upper - lower + 1)) + lower; | |
| } | |
| function setLanguage(language, updateResult) { | |
| switch (language) { | |
| case "german": | |
| translate.calcOptions = ["+ Addition", "- Subtraktion", "\xB7 Multiplikation", ": Division"]; | |
| translate.timeString = "Zeitmessung (10 Fragen)"; | |
| translate.positiveComment = ["Bravo!", "Sehr gut!", "Prima!", "Na also, es geht doch!", "Ausgezeichnet!"]; | |
| translate.negativeComment = ["Nicht so schnell ... Versuch es noch einmal!", | |
| "Du Schlafmütze ... Mach das bitte nochmal!", | |
| "Leider falsch ...noch ein Versuch!", | |
| "Bist Du sicher? Schau nochmal genau hin!", | |
| "Falsch ... bitte jetzt aber richtig!"]; | |
| translate.infoComment = "Bitte klicke auf die richtige Antwort!"; | |
| translate.resultComment = "Ergebnis"; | |
| translate.menuTitle = "Menü"; | |
| translate.soundLabel = "Ton"; | |
| translate.correctResults = "Richtige Antworten:"; | |
| translate.incorrectResults = "Falsche Antworten:"; | |
| translate.timeResults = "Zeit:"; | |
| break; | |
| case "english": | |
| translate.calcOptions = ["+ summation", "- subtraction", "\xB7 multiplication", ": division"]; | |
| translate.timeString = "timer (10 questions)"; | |
| translate.positiveComment = ["Great!", "Very good!", "Excellent!", "I told you, you can do it!", "Terrific!"]; | |
| translate.negativeComment = ["Not so fast ... Try it again!", | |
| "Hey, wake up ... Try it again, please!", | |
| "Unfortunately wrong ... one more try!", | |
| "Are you sure? Think twice!", | |
| "Wrong ... please do it right next time!"]; | |
| translate.infoComment = "Please click on the correct answer!"; | |
| translate.resultComment = "Result"; | |
| translate.menuTitle = "Menu:"; | |
| translate.soundLabel = "sound"; | |
| translate.correctResults = "Right answers:"; | |
| translate.incorrectResults = "Wrong answers:"; | |
| translate.timeResults = "Timer:"; | |
| break; | |
| case "french": | |
| translate.calcOptions = [ "+ addition", "- soustraction", "\ xB7 multiplication", ": division"]; | |
| translate.timeString = "chronomètre (10 secondes)"; | |
| translate.positiveComment = ["Génial!", "Très bien!", "Excellent!", "Je vous ai dit, vous pouvez le faire!", "Super!"]; | |
| translate.negativeComment = ["Pas si vite ... Essayez à nouveau!", | |
| "Hey, réveille-toi ... Essayez à nouveau, s'il vous plaît!", | |
| "Malheureusement mal ... encore un essai!", | |
| "Êtes-vous sûr? Réfléchissez à deux fois!", | |
| "Mauvais ... s'il vous plaît le faire dès la prochaine fois!"]; | |
| translate.infoComment = "S'il vous plaît cliquer sur la bonne réponse!"; | |
| translate.resultComment = "Résultat"; | |
| translate.menuTitle = "Menu:"; | |
| translate.soundLabel = "sonner"; | |
| translate.correctResults = "Bonnes réponses:"; | |
| translate.incorrectResults = "Mauvaises réponses:"; | |
| translate.timeResults = "Chronomètre:"; | |
| break; | |
| case "hebrew": | |
| translate.calcOptions = ["+ בנוסף", "- חיסור", "\xB7 כפל", ": חלוק"]; | |
| translate.timeString = "שָׁעוֹן עֶצֶר"; | |
| translate.positiveComment = ["!שַׁפִּיר", "!טוב מאוד", "!מְצוּיָן", "!אמרתי לך, אתה יכול לעשות את זה", "!מושלם"]; | |
| translate.negativeComment = ["!לא כל כך מהר - לנסות את זה שוב", | |
| "!היי, תתעורר ... נסה את זה שוב, בבקשה", | |
| "!למרבה הצער לא בסדר ... עוד צ'אנס אחד", | |
| "!האם אתה בטוח? תחשוב פעמיים", | |
| "!בזמן הלא נכון ... אנא עשה זאת ממש ליד"]; | |
| translate.infoComment = "!אנא לחץ על התשובה הנכונה"; | |
| translate.resultComment = "תוֹצָאָה"; | |
| translate.menuTitle = ":תַפרִיט"; | |
| translate.soundLabel = "נשמע"; | |
| translate.correctResults = ":נכון תשובות"; | |
| translate.incorrectResults = ":תשובות לא נכונות"; | |
| translate.timeResults = ":שָׁעוֹן עֶצֶר"; | |
| break; | |
| case "spanish": | |
| translate.calcOptions = ["+ suma", "- sustracción", "\xB7 multiplicación", ": división"]; | |
| translate.timeString = "cronógrafo (10 preguntas)"; | |
| translate.positiveComment = ["Estupendo!", "Muy bien!", "Excelente!", "Te lo dije, usted puede hacerlo!", "Terrífico!"]; | |
| translate.negativeComment = ["No tan rápido ... Inténtelo de nuevo!", | |
| "Hey, despierta ... Inténtelo de nuevo, por favor!", | |
| "Por desgracia mal ... otra oportunidad!", | |
| "¿Estás seguro? ¡Pensar dos veces!", | |
| "Mal ... por favor, hacerlo bien la próxima vez!"]; | |
| translate.infoComment = "Por favor, haga clic en la respuesta correcta!"; | |
| translate.resultComment = "Resultado"; | |
| translate.menuTitle = "Menú:"; | |
| translate.soundLabel = "sonar"; | |
| translate.correctResults = "Respuestas correctas:"; | |
| translate.incorrectResults = "Respuestas incorrectas:"; | |
| translate.timeResults = "Cronógrafo:"; | |
| break; | |
| case "arabic": | |
| translate.calcOptions = ["+ خلاصة", "- طرح", "\xB7 ضرب", ": عملية القسمة"]; | |
| translate.timeString = "الكونومتر الميقات"; | |
| translate.positiveComment = ["!عظيم", "!جيد جدا", "!ممتاز", "!قلت لك، يمكنك أن تفعل ذلك", "!رائع"]; | |
| translate.negativeComment = ["!ليس بهذه السرعة ... حاول مرة أخرى", | |
| "!مهلا، أستيقظ ... حاول مرة أخرى، من فضلك", | |
| "!خاطئة للأسف ... واحدة محاولة أكثر", | |
| "!هل أنت واثق؟ فكر مرتين", | |
| "!خطأ ... يرجى القيام بذلك الوقت الحق المقبل"]; | |
| translate.infoComment = "!الرجاء النقر على الإجابة الصحيحة"; | |
| translate.resultComment = "نتيجة"; | |
| translate.menuTitle = ":قائمة طعام"; | |
| translate.soundLabel = "صوت"; | |
| translate.correctResults = ":الإجابات الصحيحة"; | |
| translate.incorrectResults = ":إجابات خاطئة"; | |
| translate.timeResults = ":الكونومتر الميقات"; | |
| break; | |
| } | |
| d3.select(".feedback").html(translate.infoComment); | |
| if (updateResult){ | |
| d3.select("div.menu").text(translate.menuTitle); | |
| var newResultComment = d3.select("div.result").html().split(":"); | |
| if (correctAnswers.length > 0 || incorrectAnswers.length > 0) { | |
| d3.select("div.result").text(translate.resultComment + ":" + (newResultComment[1] ? newResultComment[1] : "")); | |
| } | |
| showResults(); | |
| d3.selectAll("select#ro option").data(translate.calcOptions).text(function(d) {return d;}); | |
| d3.select("label.time").text(translate.timeString); | |
| d3.select("label.sound").text(translate.soundLabel); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment