Generate a multiple choice quiz with REST Countries API JSON data using Vanilla Javascript. Randomize order of answers - javascript

I'm trying to build a geography game/quiz using the Google Maps API and the REST Countries API as well. I have the Google Maps functionality working along with the REST Countries API as well. What's happening is, on each page refresh, a random country loads and data from the REST Countries API generates (it's hidden so the user will only get hints as to what country it is). The map also pans to the center of the random country(hybrid image only with no country label). This is exactly what I want to happen.
I have a loop in my Javascript pulling countries (answers) for the multiple choice section but it's only pulling from the beginning. Is there a way to randomize the label and insert the correct answer (countryData.name) in there somewhere? I had this working but my code was not dry at all and the first selection was always the right answer. Not sure how to get the loop to pull answers at random either. This is the last thing I need to do before I can start styling and fixing the point system. Sorry, had to hide my Google API key.
const countriesList = document.getElementById("countries");
let gameTitle = document.querySelector(".gameTitle");
let selectedCountry;
let updateScore = document.querySelector(".score");
let guesses = document.querySelector(".wrongAnswers");
let score = 0;
let wrongAnswers = 0;
// Rest Countries
function newQuestion() {
fetch("https://restcountries.eu/rest/v2/all")
.then(response => response.json())
.then(data => initialize(data))
.catch(error => console.log("Error:", error));
function initialize(countriesData) {
// Define countries
countries = countriesData;
// Create an empty string where you add your option value in at random as a string
let options = "";
countries.forEach(country => options += `<option value="${country.alpha3Code}">${country.name}</option>`);
countriesList.innerHTML = options;
// Random index of one instance of the API call
countriesList.selectedIndex = Math.floor(Math.random() * countriesList.length);
// Set or return the index of the selected value
// for display card
displayCountryInfo(countriesList[countriesList.selectedIndex].value);
// displayCountryInfo(countriesList.value);
}
function displayCountryInfo(countryByAlpha3Code) {
let addQuestions = document.querySelector(".question");
const countryData = countries.find(country => country.alpha3Code === countryByAlpha3Code);
selectedCountry = countryData.name;
document.querySelector("#flag-container img").src = countryData.flag;
// If the flag fails to load, display the country name
document.querySelector("#flag-container img").alt = `flag of ${countryData.name}`;
document.getElementById("country").innerHTML = countryData.name;
document.getElementById("capital").innerHTML = countryData.capital;
document.getElementById("population").innerHTML = countryData.population.toLocaleString("en-US");
document.getElementById("currencies").innerHTML = countryData.currencies.filter(c => c.name).map(c => `${c.name} (${c.code})`).join(', ');
let languages = document.getElementById("languages").innerHTML = countryData.languages.filter(l => l.name).map(l => `${l.name}`).join(', ');
document.getElementById("region").innerHTML = countryData.region;
document.getElementById("subregion").innerHTML = countryData.subregion;
document.getElementById("lat-long").innerHTML = countryData.latlng;
initMap(countryData);
addQuestions.innerHTML = `I am located in ${countryData.subregion}. There are ${countryData.languages.length} language(s) spoken here: ${languages}. My capital city is ${countryData.capital}. What's my name?`;
function multipleChoice() {
for (let i = 0; i < 7; i++) {
let $input = document.querySelector('#inputs');
$input.innerHTML = $input.innerHTML + `<input id='choice${i}' name='countries' type='radio' onchange='getValue(this)' value='${countries[i].name}'/> ${countries[i].name}`;
}
}
multipleChoice();
}
// Access Google Maps API
function initMap(country) {
// Create a variable
let myLatLng = new google.maps.LatLng(country.latlng[0], country.latlng[1]);
//object literal
//instantiate map with mapOptions object
let mapOptions = {
center: myLatLng,
zoom: 5,
disableDefaultUI: true,
mapTypeId: 'satellite',
heading: 90,
tilt: 45,
rotateControl: true,
}
let marker = new google.maps.Marker({
position: myLatLng
});
// Create map
let map = new google.maps.Map(document.getElementById("mapDiv"), mapOptions);
// Set marker
marker.setMap(map);
}
}
newQuestion();
function getValue(element) {
if (element.value === selectedCountry) {
score++;
updateScore.innerHTML = `Score: ${score}`;
newQuestion();
if (score === 10) {
gameTitle.innerHTML = "You Won!";
}
} else {
wrongAnswers++;
guesses.innerHTML = `Wrong guesses ${wrongAnswers}`
newQuestion();
if (wrongAnswers === 3) {
gameTitle.innerHTML = "Game Over!";
}
}
}
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 15px;
background: rgb(51, 45, 45);
height: 100%;
margin: 0;
padding: 0;
}
p {
padding: 0 2.5rem 2.5rem;
margin: 0;
}
h3, .score, .wrongAnswers {
text-align: center;
}
html {
font-family: 'Dosis', sans-serif;
height: 100%;
}
#mapDiv {
height: 50%;
}
#main-container {
display: none;
float: right;
width: 502px;
margin: 30px auto;
padding: 0;
}
#flag-container {
float: left;
height: 252px;
width: 502px;
background-color: rgb(19, 16, 16);
border: 10px solid rgb(32, 13, 28);
box-shadow: 2px 4px 25px rgb(27, 4, 4);
}
#flag-container img {
width: 100%;
height: 100%;
}
#quiz-container {
color: white;
background: rgb(51, 45, 45);
overflow: hidden;
}
#media screen and (max-width: 768px) {
body {
font-size: 12px;
}
#main-container {
width: 342px;
}
#flag-container {
height: 172px;
width: 50%;
}
#info-container select {
font-size: 12px; font-weight: 600;
}
}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MapApp Quiz</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="main.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> -->
</head>
<body>
<div id="mapDiv"><h1></h1></div>
<div id="root"></div>
<div id="flag-container">
<img src="" alt="">
</div>
<div id="main-container">
<div class="card">
<div id="info-container">
<select id="countries"></select>
<p>Country: <span id="country"></span></p>
<p>Capital: <span id="capital"></span></p>
<p>Population: <span id="population"></span></p>
<p>Currencies: <span id="currencies"></span></p>
<p>Languages: <span id="languages"></span></p>
<p>Region: <span id="region"></span></p>
<p>Subregion: <span id="subregion"></span></p>
<p>Lat/Long: <span id="lat-long"></span></p>
</div>
</div>
</div>
<div id="quiz-container">
<h3 class="gameTitle">MapApp Quiz</h3>
<h5 class="score">Score: 0</h5>
<h5 class="wrongAnswers">Wrong guesses: 0</h5>
<p class="question"></p>
<form id="inputs">
</form>
</div>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=[API_KEY]"></script>
<script src="main.js" async defer></script>
</body>
</html>

Ok, so it's not clear to me how you're selecting the "correct" answer, but let's assume you have one country that is the answer and then the list of all countries. You could make a list of 4 random countries plus the correct country like so:
function generateAnswers(answer, allCountries) {
let wrongCountries = allCountries.filter(country => country.alpha3Code !== answer.alpha3Code);
const countOfAnswers = 5;
const positionOfCorrectAnswer = Math.floor(Math.random() * (countOfAnswers + 1));
const answers = [];
for(let i=0; i< countOfAnswers; i++) {
if (i === positionOfCorrectAnswer) {
answers.push(answer);
} else {
let randomAnswer = wrongCountries[Math.floor(Math.random() * wrongCountries.length)];
wrongCountries = wrongCountries.filter(country => country.alpha3Code !== randomAnswer.alpha3Code);
answers.push({ name: randomAnswer.name, alpha3Code: randomAnswer.alpha3Code } );
}
}
return answers;
};
Ok, so this function should return you an array of 5 countries including the right answer and 4 random wrong answers. You can call this in your code that builds the options and build the HTML from it, rather than using the full list of countries.

Related

How can I prevent 2 svg paths from being connected?

I am creating some kind of signature field, but I have the following unwanted behavior: when I draw for example 2 lines, at the beginning the second one looks like it is trying to connect to the first one and my question is how can I prevent that?
...in the example below I actually tried to draw 3 lines.
... Edit: I've edited and improved the example below based on the suggestions from #Danny'365CSI'Engelman and #chrwahl
and it now works for both desktop and mobile.
let signature = document.getElementById('signature');
if (signature) {
const appendNewSvgPath = function (e) {
if (e.buttons === 1) {
let intiialXY = ` ${e.offsetX} ${e.offsetY}`;
svgpath = document.createElementNS("http://www.w3.org/2000/svg", "path");
svgpath.setAttribute('d', `M${intiialXY} C${intiialXY.repeat(3)}`);
signature.append(svgpath);
}
};
const drawToSvgPath = function (e) {
if (e.buttons === 1) {
params.push(e.offsetX);
params.push(e.offsetY);
if (params.length === 6) {
let d = svgpath.getAttribute('d');
svgpath.setAttribute('d', `${d} ${params.join(' ')}`);
params = [];
}
}
};
const clearPathParams = _ => params = [];
let svgpath;
let params = [];
signature.onmousedown = appendNewSvgPath;
signature.onpointerdown = appendNewSvgPath;
signature.onmousemove = drawToSvgPath;
signature.onpointermove = drawToSvgPath;
signature.onmouseout = clearPathParams;
signature.onpointerout = clearPathParams;
signature.onmouseenter = appendNewSvgPath;
signature.onpointerenter = appendNewSvgPath;
signature.onmouseup = clearPathParams;
signature.onpointerup = clearPathParams;
}
body {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
height: 100vh;
overflow: hidden;
background-color: whitesmoke;
}
#signature {
display: block;
width: 800px;
height: 300px;
border: none;
background-color: white;
touch-action: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<svg id="signature" stroke="blue" stroke-width="2" stroke-linecap="round" fill="none"></svg>
</body>
</html>
In some cases the params array is not empty when when the mousedown event happens because the array is only emptied when it has the length of 6 (in the mousemove event listener). To avoid a partly filled array you should empty it on the mouse up event.
let signature = document.getElementById('signature');
let svgpath;
let params = [];
if (signature) {
signature.addEventListener('mousedown', e => {
if (e.buttons === 1) {
svgpath = document.createElementNS("http://www.w3.org/2000/svg", "path");
svgpath.setAttribute('d', `M ${e.offsetX} ${e.offsetY}`);
signature.append(svgpath);
}
});
signature.addEventListener('mousemove', e => {
if (e.buttons === 1) {
params.push(e.offsetX);
params.push(e.offsetY);
if (params.length == 6) {
let d = svgpath.getAttribute('d');
svgpath.setAttribute('d', `${d} C ${params.join(' ')}`);
params = [];
}
}
});
signature.addEventListener('mouseup', e => {
params = [];
});
}
body {
margin: 0;
padding: 0;
background-color: whitesmoke;
}
#signature {
display: block;
width: 800px;
height: 300px;
border: none;
background-color: white;
}
<svg id="signature" stroke="blue" stroke-width="2" stroke-linecap="round" fill="none"></svg>

Error on trying skip dialogue on my visual novel engine

I'm trying make my own visual novel engine for my game but i has some problem in my code,
In normally, text is writing like keyword write but when you press on anywhere on screen text complate automaticly. But when i clicked texts will combime and become like random text.
script.js
import {init} from "./main.js"
import Dialouge from "./dialogue.js"
let dialogues = [
"Merhaba buralarda yenisin galiba?",
"Tanıtmamı istermisin?"
]
const {body,head} = document
const html = head.parentNode
init(html,body)
let d = new Dialouge(body)
d.create()
let x = d.updateText(dialogues[0]);
let i = 1;
let sp = d.props.charPerSecond
document.onclick = () => {
if (x.lastStatus == false) {
if (d.props.charPerSecond == sp * 2) return;
setText({
charPerSecond: sp * 2
})
while(x.lastStatus==false) {}
setText({
charPerSecond: sp
})
} else {
x = d.updateText(dialogues[i])
i++;
}
}
main.js
export function init(html,body) {
html.style.height = body.style.height = "100%"
}
dialogue.js
const timer = ms => new Promise(res => setTimeout(res, ms))
export default class Dialogue {
#body
#element
#inner
#textP
constructor(body) {
this.#body = body
this.#element = document.createElement("div")
this.#inner = document.createElement("div")
this.#textP = {
speed: 0.1,
charPerSecond: 1
}
this.lastStatus = true
}
get props() {
return this.#textP
}
create() {
this.#body.style.position = "relative"
let element = this.#element
this.#inner.classList.add("dialouge-inner")
element.classList.add("dialogue")
element.append(this.#inner)
this.#body.append(element)
}
setText(data) {
this.#textP = {
...this.#textP,
...data
}
}
async updateText (datas) {
this.lastStatus = false
return new Promise(async res => {
datas = datas.split('')
this.#inner.innerText = ""
for (let i in datas) {
let data = datas.slice(i)
for (let j = 0; j < this.#textP.charPerSecond; j++) {
this.#inner.textContent += data[j]
}
await timer(this.#textP.speed * 1000);
}
this.lastStatus = true
res()
})
}
}
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>replit</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body></body>
<script src="script.js" type="module"></script>
</html>
css
html,body {
overflow:hidden;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.dialogue {
position: absolute;
padding: 4px;
background: #000;
bottom: 1vmin;
left: 0;
width: 100%;
height: 20vmin;
}
.dialouge-inner {
width: 50%;
height: 95%;
margin: auto auto;
background: white;
}

How to call certain chord buttons instead of all the chord buttons from the chord library?

I have been able to build a chord library that plays the chord and also says the chord name. I'm now trying to build upon the chord library. How can I have only a limited amount of chord buttons be displayed on the UI instead of all of the chords that are being populated?
The top portion of the code (that is below) is displaying the four buttons but not playing the chord sounds or saying the chord names.
The other code after is showing all the chord buttons and playing the chord sounds and chord names.
We're trying to combine the two portions of code from below so that they play the chord names and sounds for the four chords above.
//In order to run in terminal insert the following code to activate localhost: npx parcel src/index.html
import { chordType, transpose, note } from '#tonaljs/tonal';
import { chord } from '#tonaljs/chord';
import { entries } from '#tonaljs/chord-dictionary';
import { Howler, howl } from 'howler';
const buttons = document.querySelector(".buttons");
const arrayOfChordNames = ["Major", "Minor", "Augmented", "Diminished"];
arrayOfChordNames.forEach((chord) => {
let buttonElement = createElement("button", chord);
buttons.appendChild(buttonElement);
});
function createElement(element, content) {
let newElement = document.createElement(element);
newElement.textContent = content;
return newElement;
}
// code above is displaying the four buttons but not playing the chord sound and chord name
//code below is showing all the chord buttons and playing the chord sound and chord names
// we're trying combine the chord from below so that it plays the audio from the chord names and sounds for the four chords above
const sound = new Howl({
src: ['assets/pianosprite.mp3'],
onload() {
console.log('Sound file has been loaded. Do something here!');
soundEngine.init();
},
onloaderror(e, msg) {
console.log('Error', e, msg);
}
});
const startNotes = ['C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#', 'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B'];
const startNoteSelector = document.querySelector('#start-note');
const octaveSelector = document.querySelector('#octave');
const buttons = document.querySelector('.buttons');
const intervalsInChord = document.querySelector('.intervals-in-chord');
const notesInChord = document.querySelector('.notes-in-chord');
let selectedStartNote = 'C';
let selectedOctave = '1';
const app = {
init() {
this.setupStartNotes();
this.setupOctaves();
this.setupButtons();
this.setupEventListeners();
},
setupStartNotes() {
startNotes.forEach(noteName => {
let noteNameOption = this.createElement('option', noteName);
startNoteSelector.appendChild(noteNameOption);
});
},
setupOctaves() {
for (let i = 1; i <= 6; i++) {
let octaveNumber = this.createElement('option', i);
octaveSelector.appendChild(octaveNumber);
}
},
setupButtons() {
const chordNames = entries().map(entry => {
return entry.aliases[0];
});
chordNames.forEach(chordName => {
let chordButton = this.createElement('button', chordName);
buttons.appendChild(chordButton);
});
},
setupEventListeners() {
startNoteSelector.addEventListener('change', () => {
selectedStartNote = startNoteSelector.value;
});
octaveSelector.addEventListener('change', () => {
selectedOctave = octaveSelector.value;
});
buttons.addEventListener('click', (event) => {
if (event.target.classList.contains('buttons')) {
return;
}
selectedChord = event.target.innerText;
this.displayAndPlayChord(selectedChord, event);
});
},
displayAndPlayChord(selectedChord, event) {
let chordIntervals = chord(selectedChord).intervals;
intervalsInChord.innerText = chordIntervals.join(' - ');
const startNoteWithOctave = selectedStartNote + selectedOctave;
let chordNotes = chordIntervals.map(val => {
return transpose(startNoteWithOctave, val);
});
notesInChord.innerText = chordNotes.join(' - ');
soundEngine.play(chordNotes, event);
},
createElement(elementName, content) {
let element = document.createElement(elementName);
element.innerHTML = content;
return element;
}
}
const soundEngine = {
init() {
const lengthOfNote = 2400;
let timeIndex = 0;
for (let i = 24; i <= 96; i++) {
sound['_sprite'][i] = [timeIndex, lengthOfNote];
timeIndex += lengthOfNote;
}
},
play(soundSequence, event) {
const buttons =
document.querySelector(".buttons");
const chordNameTable = {
"M": "Major",
"m": "Minor",
"dim": "diminished",
"aug": "Augmented"
}
buttons.addEventListener("click", (event) => {
})
function textToSpeech(message, chord) {
const speech = new SpeechSynthesisUtterance();
speech.lang = "en-US";
speech.text = message;
speech.volume = 1;
speech.rate = 1;
speech.pitch = 1;
window.speechSynthesis.speak(speech);
// When speaking has finished
speech.onend = function() {
playChord(chord);
}
}
function playChord(chord) {
// Wait a second (1000 miliseconds) before playing the chord
setTimeout(() => {
const chordMidiNumbers = soundSequence.map(noteName => {
return note(noteName).midi;
});
sound.volume(0.05);
chordMidiNumbers.forEach(noteMidiNumber => {
sound.play(noteMidiNumber.toString());
});
console.log("Chord to be played", chord);
}, 500);
}
const sayThis = chordNameTable[event.target.textContent];
textToSpeech(sayThis, event.target.textContent);
}
}
app.init();
const allChordNames = entries()
chordEntries.map(entry => {
return entry.aliases[0];
})
console.log(Array.isArray (allChordNames));
body {
font-family: Lato, Sans-serif;
color: #fff;
background: #000000
}
.controls{
display: flex;
justify-content: center;
}
.controls select {
margin: 0 10px;
}
.chord-notes {
display: flex;
flex-direction: column;
align-items: center;
font-size: 1.5em;
}
.chord-notes p {
margin: 10px 0;
}
/*width makes the page more condensed towards the left*/
.buttons {
display: flex;
flex-wrap: wrap;
width: 60%;
}
/*min-width divides columns into 3 instead of 4, max-width creates a set size for the button*/
.buttons button{
flex-grow: 1;
min-width: 20%;
max-width: 20%;
height: 50px;
font-size: 1em;
background: #7200CC;
border: none;
color: #fff;
margin: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chord Dictionary</title>
<link rel="stylesheet" href="styles.css">
<head>
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover {
background-color: #A400DD;
}
</style>
</head>
</head>
<ul>
<li><a class="active" href="/home">Home</a></li>
<li>Lessons</li>
<li>Practice</li>
<li>Test</li>
<li>Demo</li>
<li>Triads</li>
<li>Sign Up</li>
<li>Login</li>
</ul>
</head>
<body>
<h1>This is the Triads page!</h1>
<div class="controls">
<label for="start-note">Start note:</label>
<select name="start note" id="start-note">
</select>
<label for="octave">Octave: </label>
<select name="Octave" id="octave">
</select>
<label for="Loops">Loops: </label>
<select id="Loops">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="loop">loop</option>
</select>
</div>
<div class="chord-notes">
<p class="notes-in-chord">C3 - D3 - E3 - F#3 - A3</p>
<p class="intervals-in-chord">P1 - M3 - P5 - M7</p>
</div>
<div class="buttons">
</div>
<script type="module" src="triads.js"></script>
</body>
</html>

Show images in quiz javascript

I'm trying to create a quiz that tests users awareness of real and fake emails. What I want to do is have the question displayed at the top saying "Real or Fake", then have an image displayed underneath which the user needs to look at to decided if it's real or fake. There are two buttons, real and fake, and regardless of whether they choose the right answer I want to swap the original image with annotated version - showing how users could spot that it was fake or real.
But I'm not sure how to show the annotated version once the answer has been submitted. Could someone help?
function Quiz(questions) {
this.score = 0;
this.questions = questions;
this.questionIndex = 0;
}
Quiz.prototype.getQuestionIndex = function() {
return this.questions[this.questionIndex];
}
Quiz.prototype.guess = function(answer) {
if (this.getQuestionIndex().isCorrectAnswer(answer)) {
this.score++;
}
this.questionIndex++;
}
Quiz.prototype.isEnded = function() {
return this.questionIndex === this.questions.length;
}
function Question(text, choices, answer) {
this.text = text;
this.choices = choices;
this.answer = answer;
}
Question.prototype.isCorrectAnswer = function(choice) {
return this.answer === choice;
}
function populate() {
if (quiz.isEnded()) {
showScores();
} else {
// show question
var element = document.getElementById("question");
element.innerHTML = quiz.getQuestionIndex().text;
// show options
var choices = quiz.getQuestionIndex().choices;
for (var i = 0; i < choices.length; i++) {
var element = document.getElementById("choice" + i);
element.innerHTML = choices[i];
guess("btn" + i, choices[i]);
}
showProgress();
}
};
function guess(id, guess) {
var button = document.getElementById(id);
button.onclick = function() {
quiz.guess(guess);
populate();
}
};
function showProgress() {
var currentQuestionNumber = quiz.questionIndex + 1;
var element = document.getElementById("progress");
element.innerHTML = "Question " + currentQuestionNumber + " of " + quiz.questions.length;
};
function showScores() {
var gameOverHTML = "<h1>Result</h1>";
gameOverHTML += "<h2 id='score'> Your scores: " + quiz.score + "</h2>";
var element = document.getElementById("quiz");
element.innerHTML = gameOverHTML;
};
// create questions here
var questions = [
new Question("<img src= 'netflix_fake.jpg' />", ["Real", "Fake"], "Fake"),
new Question("<img src= 'dropbox_real.jpg' />", ["Real", "Fake"], "Real"),
new Question("<img src= 'gov_real.jpg' />", ["Real", "Fake"], "Real"),
new Question("<img src= 'paypal_fake.jpg' />", ["Real", "Fake"], "Fake"),
new Question("<img src= 'gmail.jpg' />", ["Real", "Fake"], "Fake")
];
//create quiz
var quiz = new Quiz(questions);
// display
populate();
body {
background-color: #538a70;
}
.grid {
width: 600px;
height: 500px;
margin: 0 auto;
background-color: #fff;
padding: 10px 50px 50px 50px;
border: 2px solid #cbcbcb;
}
.grid h1 {
font-family: "sans-serif";
font-size: 60px;
text-align: center;
color: #000000;
padding: 2px 0px;
}
#score {
color: #000000;
text-align: center;
font-size: 30px;
}
.grid #question {
font-family: "monospace";
font-size: 30px;
color: #000000;
}
.buttons {
margin-top: 30px;
}
#btn0,
#btn1,
#btn2,
#btn3 {
background-color: #a0a0a0;
width: 250px;
font-size: 20px;
color: #fff;
border: 1px solid #1D3C6A;
margin: 10px 40px 10px 0px;
padding: 10px 10px;
}
#btn0:hover,
#btn1:hover,
#btn2:hover,
#btn3:hover {
cursor: pointer;
background-color: #00994d;
}
#btn0:focus,
#btn1:focus,
#btn2:focus,
#btn3:focus {
outline: 0;
}
#progress {
color: #2b2b2b;
font-size: 18px;
}
<div class="grid">
<div id="quiz">
<h1>Can you spot the fake email?</h1>
<hr style="margin-bottom: 20px">
<p id="question"></p>
<div class="buttons">
<button id="btn0"><span id="choice0"></span></button>
<button id="btn1"><span id="choice1"></span></button>
</div>
<hr style="margin-top: 50px">
<footer>
<p id="progress">Question x of y</p>
</footer>
</div>
</div>
When user clicks button I trigger class and I add it second name, on second I have written to get swapped, I wrote you basically full project, and please read the whole comments, to understand logic
//Calling Elements from DOM
const button = document.querySelectorAll(".check");
const images = document.querySelectorAll(".image");
const answer = document.querySelector("h1");
//Declaring variable to randomly insert any object there to insert source in DOM Image sources
let PreparedPhotos;
//Our Images Sources and With them are its fake or not
//fake: true - yes its fake
//fake: false - no its real
const image = [
[
{
src:
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/1200px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
fake: true
},
{
src:
"http://graphics8.nytimes.com/images/2012/04/13/world/europe/mona-lisa-like-new-images/mona-lisa-like-new-images-custom4-v3.jpg",
fake: false
}
],
[
{
src:
"https://cdn.shopify.com/s/files/1/0849/4704/files/Creacion_de_Adan__Miguel_Angel_f5adb235-bfa8-4caa-8ffb-c5328cbad953_grande.jpg?12799626327330268216",
fake: false
},
{
src:
"https://cdn.shopify.com/s/files/1/0849/4704/files/First-image_Fb-size_grande.jpg?10773543754915177139",
fake: true
}
]
];
//Genrating Random Photo on HTML
function setRandomPhoto() {
//Random Number which will be length of our array of Object
//if you array includes 20 object it will generate random number
// 0 - 19
const randomNumber = Math.floor(Math.random() * image.length);
//Decalaring our already set variable as Array Object
PreparedPhoto = image[randomNumber];
//Our first DOM Image is Variables first object source
images[0].src = PreparedPhoto[0].src;
//and next image is next object source
images[1].src = PreparedPhoto[1].src;
}
//when windows successfully loads, up function runs
window.addEventListener("load", () => {
setRandomPhoto();
});
//buttons click
//forEach is High Order method, basically this is for Loop but when you want to
//trigger click use forEach - (e) is single button whic will be clicked
button.forEach((e) => {
e.addEventListener("click", () => {
//decalring variable before using it
let filtered;
//finding from our DOM image source if in our long array exists
//same string or not as Image.src
//if it exists filtered variable get declared with that found obect
for (let i = 0; i < image.length; i++) {
for (let k = 0; k < 2; k++) {
if (image[i][k].src === images[0].src) {
filtered = image[i][k];
}
}
}
//basic if else statement, if clicked button is Fake and image is true
//it outputs You are correct
//if clicked button is Real and Image is false it outputs Correct
//Else its false
//Our image checking comes from filtered variable
if (e.innerText === "Fake" && filtered.fake === true) {
answer.innerText = "You Are Correct";
images.forEach((image) => {
image.classList.toggle("hidden");
});
} else if (e.innerText === "Real" && filtered.fake === false) {
answer.innerText = "You Are Correct";
images.forEach((image) => {
image.classList.toggle("hidden");
});
} else {
answer.innerHTML = "You are Wrong";
images.forEach((image) => {
image.classList.toggle("hidden");
});
}
});
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
min-height: 100vh;
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
}
.image-fluid {
display: flex;
}
.image-fluid .image {
width: 200px;
margin: 0 10px;
transition: 0.5s;
}
.image-fluid .image:nth-child(1).hidden {
transform: translateX(110px);
}
.image-fluid .image:nth-child(2).hidden {
transform: translateX(-110px);
}
<div class="container">
<div class="image-fluid">
<img src="" class="image hidden">
<img src="" class="image hidden">
</div>
<div class="button-fluid">
<button class="check">Fake</button>
<button class="check">Real</button>
</div>
</div>
<h1></h1>

Annotate RoughViz chart using Rough-Notation library

I have a simple bar chart built with RoughViz.js. The key takeaway that I'd like to highlight in the chart is the difference in height between the first bar and the third bar. To do this I'd like to use the bracket annotation from Rough Notation and set the bracket to start at a y-coordiate equal to the height of the first bar and end at a y-coordinate equal to the height of the last bar. What is the best way to accomplish this?
EDIT...
this picture illustrates what I'm trying to accomplish. The large bracket is the one that the rough-notation library is drawing in my code. Note that it wraps the entire chart. I want it to instead draw the bracket like the small dark blue one that I've mocked up. The dashed lines are also just mock up so as to better convey the desired positioning.
The external libraries are:
https://github.com/jwilber/roughViz and
https://github.com/rough-stuff/rough-notation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/rough-viz#1.0.6"></script>
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
<style>
body {}
;
.item1 {
grid-area: chart;
}
.item2 {
grid-area: annotation;
}
.grid-container {
margin-top: 3rem;
display: grid;
grid-template-areas:
'chart annotation';
grid-template-rows: 1fr;
grid-template-columns: 8fr, 3fr;
}
#typedtext {
font-family: 'Waiting for the Sunrise', cursive;
font-size: 25px;
margin: 10px 50px;
letter-spacing: 6px;
font-weight: bold;
color: blue;
padding-left: 3rem;
padding-top: 30%;
height: 100%;
}
</style>
</head>
<body>
<button id="annotate-button">Click me</button>
<div class="grid-container">
<div class="item1">
<div id="viz0"></div>
</div>
<div class="item2">
<div id="typedtext"></div>
</div>
</div>
</body>
</html>
<script>
// create Bar chart from csv file, using default options
new roughViz.Bar({
element: '#viz0', // container selection
data: {
labels: ['First thing', 'Second thing', 'Third thing'],
values: [100, 50, 25]
},
width: window.innerWidth * .7,
height: window.innerHeight * .7
});
</script>
<script type="module">
import { annotate } from 'https://unpkg.com/rough-notation?module';
const e = document.querySelector('#viz0');
const annotation = annotate(e, { type: 'bracket', color: 'blue', padding: [2, 10], strokeWidth: 3 });
document.getElementById("annotate-button").addEventListener('click', function(){
annotation.show();
})
</script>
<script>
//https://css-tricks.com/snippets/css/typewriter-effect/
// set up text to print, each item in array is new line
var aText = new Array(
"This is a comment"
);
var iSpeed = 10; // time delay of print out
var iIndex = 0; // start printing array at this posision
var iArrLength = aText[0].length; // the length of the text array
var iScrollAt = 20; // start scrolling up at this many lines
var iTextPos = 0; // initialise text position
var sContents = ''; // initialise contents variable
var iRow; // initialise current row
function typewriter() {
sContents = ' ';
iRow = Math.max(0, iIndex - iScrollAt);
var destination = document.getElementById("typedtext");
while (iRow < iIndex) {
sContents += aText[iRow++] + '<br />';
}
destination.innerHTML = sContents + aText[iIndex].substring(0, iTextPos) + "_";
if (iTextPos++ == iArrLength) {
iTextPos = 0;
iIndex++;
if (iIndex != aText.length) {
iArrLength = aText[iIndex].length;
setTimeout("typewriter()", 500);
}
} else {
setTimeout("typewriter()", iSpeed);
}
}
document.getElementById("annotate-button").addEventListener('click', function() {
typewriter();
})
</script>
You can also click here for a JsFiddle with the same code. The chart renders a little better on JsFiddle. https://jsfiddle.net/hughesdan/bmyda74e/1/
You can try this. You will find additional/changed code documented properly. It would be better to test the code here: https://jsfiddle.net/fo16n8tu/.
Rough annotation plugin needs HTML/SVG element to select and annotate. Therefore, I add a dummy <rect> element and use it for annotation. Let me know if this approach could work for you.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/rough-viz#1.0.6"></script>
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
<style>
body {}
.item1 {
grid-area: chart;
}
.item2 {
grid-area: annotation;
}
.grid-container {
margin-top: 3rem;
display: grid;
grid-template-areas:
'chart annotation';
grid-template-rows: 1fr;
grid-template-columns: 8fr, 3fr;
}
#typedtext {
font-family: 'Waiting for the Sunrise', cursive;
font-size: 25px;
margin: 10px 50px;
letter-spacing: 6px;
font-weight: bold;
color: blue;
padding-left: 3rem;
padding-top: 30%;
height: 100%;
}
</style>
</head>
<body>
<button id="annotate-button">Click me</button>
<div class="grid-container">
<div class="item1">
<div id="viz0"></div>
</div>
<div class="item2">
<div id="typedtext"></div>
</div>
</div>
</body>
</html>
<script>
// create Bar chart from csv file, using default options
const viz = new roughViz.Bar({
element: '#viz0', // container selection
data: {
labels: ['First thing', 'Second thing', 'Third thing'],
values: [100, 50, 25]
},
width: window.innerWidth * .7,
height: window.innerHeight * .7
});
</script>
<script type="module">
import { annotate } from 'https://unpkg.com/rough-notation?module';
const e = document.querySelector('#viz0');
/* START additional code */
// Select <g id="#viz0_svg"> element
const svgGroupWrapper = e.querySelector('[id="#viz0_svg"]');
// Get each bar <g> element
const bars = svgGroupWrapper.getElementsByClassName('viz0');
// Height difference between the first (bars[0]) and the third bar (bars[2])
const diffHeight = bars[0].getBoundingClientRect().height - bars[2].getBoundingClientRect().height;
// Get the position annotation element by horizontal x-axis
const offsetX = svgGroupWrapper.getElementsByClassName('rough-xAxisviz0')[0].getBoundingClientRect().width;
// Width of annotation element (also used in annotation options!!!)
const annotationPadding = [0, 6];
// Create dummy <rect> element to annotate on it. Annotation needs an HTML element to work
const elementToAnnotate = document.createElementNS('http://www.w3.org/2000/svg','rect');
// Set "x" attribute to offsetX - annotationPadding[1] - 1 to shift <rect> to very right, but with place for annotaion and width of rect (=1)
elementToAnnotate.setAttribute('x', offsetX - annotationPadding[1] - 1);
// Just positioning "y" to 0 so that the element start from top of the SVG
elementToAnnotate.setAttribute('y', '0');
// Set "width" to 1 to be identifable in SVG for annotation plugin
elementToAnnotate.setAttribute('width', '1');
// Set "height" to the height difference value between first and third bars
elementToAnnotate.setAttribute('height', diffHeight);
// Set "fill" to transparent so that the <rect> element is not visible for users
elementToAnnotate.setAttribute('fill', 'transparent');
// Add the element in wrapper <g> element
svgGroupWrapper.appendChild(elementToAnnotate);
/* END additional code */
// CHANGED CODE: Annotate dummy <rect> element for desired effects
const annotation = annotate(elementToAnnotate, { type: 'bracket', color: 'blue', padding: annotationPadding, strokeWidth: 3 });
document.getElementById("annotate-button").addEventListener('click', function(){
annotation.show();
})
</script>
<script>
//https://css-tricks.com/snippets/css/typewriter-effect/
// set up text to print, each item in array is new line
var aText = new Array(
"This is a comment"
);
var iSpeed = 10; // time delay of print out
var iIndex = 0; // start printing array at this posision
var iArrLength = aText[0].length; // the length of the text array
var iScrollAt = 20; // start scrolling up at this many lines
var iTextPos = 0; // initialise text position
var sContents = ''; // initialise contents variable
var iRow; // initialise current row
function typewriter() {
sContents = ' ';
iRow = Math.max(0, iIndex - iScrollAt);
var destination = document.getElementById("typedtext");
while (iRow < iIndex) {
sContents += aText[iRow++] + '<br />';
}
destination.innerHTML = sContents + aText[iIndex].substring(0, iTextPos) + "_";
if (iTextPos++ == iArrLength) {
iTextPos = 0;
iIndex++;
if (iIndex != aText.length) {
iArrLength = aText[iIndex].length;
setTimeout("typewriter()", 500);
}
} else {
setTimeout("typewriter()", iSpeed);
}
}
document.getElementById("annotate-button").addEventListener('click', function() {
typewriter();
})
</script>
You could try something like in the following example:
const lbls = ['First thing', 'Second thing', 'Third thing'];
const destination = document.getElementById("typedtext");
const empty = (data) => {
switch (true) {
case (data == null || data == 'undefined' || data == false || data == ''):
return true;
case (Array.isArray(data)):
return data.length == 0;
case (typeof data == 'object'):
return (Object.keys(data).length == 0 && data.constructor == Object);
case (typeof data == 'string'):
return data.length == 0;
case (typeof data == 'number' && !isNaN(data)):
return data == 0;
default:
return false;
}
}
const typewriter = () => {
settings.sContents = ' ';
settings.iRow = Math.max(0, settings.iIndex - settings.iScrollAt);
while (settings.iRow < settings.iIndex) {
settings.sContents += settings.aText[settings.iRow++] + '<br />';
}
if(!empty(settings.aText[settings.iIndex])) {
destination.textContent = settings.sContents + settings.aText[settings.iIndex].substring(0, settings.iTextPos) + "_";
}
if (settings.iTextPos++ == settings.iArrLength) {
settings.iTextPos = 0;
settings.iIndex++;
if (!empty(settings.aText[settings.iIndex]) && settings.iIndex != settings.aText.length) {
settings.iArrLength = settings.aText[settings.iIndex].length;
setTimeout(typewriter(), 500);
}
} else {
setTimeout(typewriter(), settings.iSpeed);
}
}
new roughViz.Bar({
element: '#viz0', // container selection
data: {
labels: lbls,
values: [100, 50, 25]
},
width: window.innerWidth * 0.7,
height: window.innerHeight * 0.7
});
var settings = {};
settings.diff = [];
lbls.forEach((label) => {
let g = document.querySelector('g[attrX=\"' + label + '\"]');
g.addEventListener('click', (e) => {
let b = parseInt(e.target.parentNode.getAttribute('attrY'));
if (e.target.getAttribute('fill') == '#ff0000') {
e.target.setAttribute('fill', 'transparent');
let index = settings.diff.indexOf(b);
settings.diff = settings.diff.filter((x, i) => i !== index);
} else {
e.target.setAttribute('fill', '#ff0000');
settings.diff.push(b);
}
});
});
document.getElementById("annotate-button").addEventListener('click', (e) => {
if (settings.diff.length == 2) {
settings.aText = [(settings.diff[0] - settings.diff[1]).toString()];
} else {
settings.aText = ['Select a pair first!'];
}
settings.iSpeed = 10; // time delay of print out
settings.iIndex = 0; // start printing array at this posision
settings.iScrollAt = 20; // start scrolling up at this many lines
settings.iTextPos = 0; // initialise text position
settings.sContents = ''; // initialise contents variable
settings.iRow = 0; // initialise current row
settings.iArrLength = settings.aText[0].length; // the length of the text array
typewriter();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/rough-viz#1.0.6"></script>
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
<script type="module">
import { annotate } from 'https://unpkg.com/rough-notation?module';
const ee = document.querySelector('#viz0');
const annotation = annotate(ee, { type: 'bracket', color: 'blue', padding: [2, 10], strokeWidth: 3 });
document.getElementById("annotate-button").addEventListener('click', function(){
annotation.show();
})
</script>
<style>
body {}
;
.item1 {
grid-area: chart;
}
.item2 {
grid-area: annotation;
}
.grid-container {
margin-top: 3rem;
display: grid;
grid-template-areas:
'chart annotation';
grid-template-rows: 1fr;
grid-template-columns: 8fr, 3fr;
}
#typedtext {
font-family: 'Waiting for the Sunrise', cursive;
font-size: 25px;
margin: 10px 50px;
letter-spacing: 6px;
font-weight: bold;
color: blue;
padding-left: 3rem;
padding-top: 30%;
height: 100%;
}
</style>
</head>
<body>
<button id="annotate-button">Click me</button>
<div class="grid-container">
<div class="item1">
<div id="viz0"></div>
</div>
<div class="item2">
<div id="typedtext"></div>
</div>
</div>
</body>
</html>
OR use this JSFiddle

Categories