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>
Related
As topic, how can I remove the quote in the picuture below or get the img element from this?
(https://i.stack.imgur.com/kdPAP.png)
I m new to javascript and start making an image slider that can pop up a window while clicked to the image.
I cloned the li object which is clicked, and try to open it in the new window, it do work but how can I get the img only without the li?
Right now i m trying to call the cloned this object with innerHTML, but it shows the img element with quote, is there any way to remove the quote?
the quote that i want to remove
first time ask a question in here, if i violate any rules in the post, i delete the post, sorry for causing any inconvenience.
Here is the code
<!DOCTYPE html>
<html>
<head>
<style>
.risuto1 {
max-width: 480px;
max-height: 270px;
margin: auto;
position: relative;
}
ul {
list-style-type: none;
margin: auto;
padding: 0;
}
.aitemu>img {
max-width: 100%;
}
.aitemu {
display: none;
position: relative;
}
.aitemu.akutibu {
display: block;
}
.risuto1>.mae,
.tsugi {
width: auto;
height: auto;
padding: 20px;
top: 102.6px;
position: absolute;
cursor: pointer;
background-color: red;
}
.risuto1>.tsugi {
left: 431.8px;
}
</style>
</head>
<body>
<script>
var risuto1 = document.createElement("div");
risuto1.classList.add("risuto1");
document.body.appendChild(risuto1);
var suraidaaitemu = document.createElement("ul");
suraidaaitemu.classList.add("suraidaaitemu");
risuto1.appendChild(suraidaaitemu);
var imejisosurisuto = ["https://pbs.twimg.com/media/CGGc3wKVEAAjmWj?format=jpg&name=medium", "https://pbs.twimg.com/media/EYCXvuzU8AEepqD?format=jpg&name=large", "https://s3-ap-northeast-1.amazonaws.com/cdn.bibi-star.jp/production/imgs/images/000/668/074/original.jpg?1626914998", "https://livedoor.blogimg.jp/rin20064/imgs/7/3/73251146.jpg", "https://www.tbs.co.jp/anime/oregairu/character/img/chara_img_02.jpg"]
//var imejirisuto=[]
for (var n = 0; n < imejisosurisuto.length; n++) {
var imeji = document.createElement("img");
imeji.src = imejisosurisuto[n];
var aitemu = document.createElement("li");
aitemu.classList.add("aitemu");
suraidaaitemu.appendChild(aitemu);
aitemu.appendChild(imeji);
aitemu.onclick=atarashivindou;
}
var akutibunasaishonoko = document.querySelector(".suraidaaitemu").firstChild;
akutibunasaishonoko.classList.add("akutibu");
var mae = document.createElement("div");
mae.classList.add("mae");
mae.innerHTML = "❮";
risuto1.appendChild(mae);
var tsugi = document.createElement("div");
tsugi.classList.add("tsugi");
tsugi.innerHTML = "❯";
risuto1.appendChild(tsugi);
var tensuu = 0;
var suraido = document.querySelector(".suraidaaitemu").children;
var gokeisuraido = suraido.length;
tsugi.onclick = function () {
Tsugi("Tsugi");
}
mae.onclick = function () {
Tsugi("Mae");
}
function Tsugi(houkou) {
if (houkou == "Tsugi") {
tensuu++;
if (tensuu == gokeisuraido) {
tensuu = 0;
}
}
else {
if (tensuu == 0) {
tensuu = gokeisuraido - 1;
}
else {
tensuu--;
}
}
for (var i = 0; i < suraido.length; i++) {
suraido[i].classList.remove("akutibu");
}
suraido[tensuu].classList.add("akutibu");
}
function atarashivindou(){
var Atarashivindou = window.open("", "_blank", "width: 1000px, max-height: 562.5px");
var kakudaigazou=document.createElement("div");
kakudaigazou.classList.add("kakudaigazou");
Atarashivindou.document.body.appendChild(kakudaigazou);
var koronaitemu=this.cloneNode(true);
var koronimeji=koronaitemu.innerHTML;
kakudaigazou.append(koronimeji);
}
</script>
</body>
.innerHTML is a string. You don't want to append a string, but the element. So change this:
var koronaitemu=this.cloneNode(true);
var koronimeji=koronaitemu.innerHTML;
kakudaigazou.append(koronimeji);
...to this:
var koronimeji=this.querySelector("img").cloneNode();
kakudaigazou.append(koronimeji);
let div = document.createElement('div');
let ul = document.createElement('ul');
const data = {};
const el = document.getElementById("name");
sortName = 0;
sortCapital = 0;
sortPopulation = 0;
sortArea = 0;
function sortByArea(data) {
var child = container.lastElementChild;
while (child) {
container.removeChild(child);
child = container.lastElementChild;
}
if(sortArea == 0){
data.sort((a, b) => {
if (a.area > b.area) return 1;
else return -1
});
return data;
}
data.sort((a, b) => {
if (a.area < b.area) return 1;
else return -1
});
return data;
}
function sortByPopulation(data) {
var child = container.lastElementChild;
while (child) {
container.removeChild(child);
child = container.lastElementChild;
}
if(sortPopulation == 0){
data.sort((a, b) => {
if (a.population > b.population) return 1;
else return -1
});
return data;
}
data.sort((a, b) => {
if (a.population < b.population) return 1;
else return -1
});
return data;
}
function sortByCapital(data) {
var child = container.lastElementChild;
while (child) {
container.removeChild(child);
child = container.lastElementChild;
}
if(sortCapital == 0){
data.sort((a, b) => {
if (a.capital > b.capital) return 1;
else return -1
});
return data;
}
data.sort((a, b) => {
if (a.capital < b.capital) return 1;
else return -1
});
return data;
}
function sortByName(data) {
var child = container.lastElementChild;
while (child) {
container.removeChild(child);
child = container.lastElementChild;
}
if(sortName == 0){
data.sort((a, b) => {
if (a.name > b.name) return 1;
else return -1
});
return data;
}
data.sort((a, b) => {
if (a.name < b.name) return 1;
else return -1
});
return data;
}
document.getElementById('area').addEventListener('click', () => f(4));
document.getElementById('population').addEventListener('click', () => f(3));
document.getElementById('capital').addEventListener('click', () => f(2));
document.getElementById('name').addEventListener('click', () => f(1));
div.appendChild(ul);
async function f(e) {
//fetching and sorting data by regions and subregions
const res = await fetch("https://restcountries.com/v3.1/all");
var data = await res.json();
const container = document.getElementById('container');
const accordion = document.createElement('div');
const olWrapper = document.getElementById('listWrapper');
const subRegionWrapper = document.getElementById('subRegionWrapper');
switch (e) {
case 1:
data = sortByName(data);
sortName += 1;
sortName %= 2;
case 2:
data = sortByCapital(data);
sortCapital += 1;
sortCapital %= 2;
case 3:
data = sortByPopulation(data);
sortPopulation += 1;
sortPopulation %= 2;
case 4:
data = sortByArea(data);
sortArea += 1;
sortArea %= 2;
}
data.sort((a, b) => {
if (a.region > b.region) return 1;
else if (a.region < b.region) return -1
else {
if (a.subregion > b.subregion) return 1;
else return -1;
}
});
const subRegions = data.reduce((r, a) => {
r[a.subregion] = r[a.subregion] || [];
r[a.subregion].push(a);
return r;
}, {});
const dropdownValues = Object.entries(subRegions);
dropdownValues.forEach(subRegion => {
const accordionWrapper = document.createElement('div');
const panel = document.createElement('div');
panel.classList.add('panel');
accordionWrapper.classList.add('accordion');
const totalArea = subRegion[1].reduce((acc, curr) => acc + curr.area, 0);
const totalPopulation = subRegion[1].reduce((acc, curr) => acc + curr.population, 0);
const li = createSubregion(subRegion[0], totalPopulation, totalArea);
accordionWrapper.appendChild(li);
accordion.appendChild(accordionWrapper);
subRegion[1].forEach(item => {
const subLi = createCountry(item.name.common, item.capital, item.area, item.population);
const subOl = document.createElement('ol');
subOl.appendChild(subLi);
panel.appendChild(subOl);
accordion.appendChild(panel);
});
accordionWrapper.addEventListener('click', function () {
this.classList.toggle("active");
const panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
});
});
container.appendChild(accordion);
}
function createSubregion(name, population, area) {
var li = document.createElement("li");
li.setAttribute("class", "subregion");
var header = document.createElement("div");
header.setAttribute("class", "subregion-header disp-flex");
var nameDiv = document.createElement("div");
var nameh2 = document.createElement("h2");
nameh2.innerText = name;
nameDiv.appendChild(nameh2);
header.append(nameDiv);
var emptyDiv = document.createElement("div");
header.appendChild(emptyDiv);
var populationDiv = document.createElement("div");
var populationh2 = document.createElement("h3");
populationh2.innerText = population;
populationDiv.appendChild(populationh2);
header.append(populationDiv);
var areaDiv = document.createElement("div");
var areah2 = document.createElement("h3");
areah2.innerText = area;
areaDiv.appendChild(areah2);
header.append(areaDiv);
li.appendChild(header);
return li;
}
function createCountry(name, capital, area, population) {
var country = document.createElement("li");
country.setAttribute("class", "country disp-flex")
var namediv = document.createElement("div");
var nameh4 = document.createElement("h4");
nameh4.innerText = name;
namediv.appendChild(nameh4);
country.appendChild(namediv);
var capitaldiv = document.createElement("div");
var capitalh4 = document.createElement("h4");
capitalh4.innerText = capital;
capitaldiv.appendChild(capitalh4);
country.appendChild(capitaldiv);
var popdiv = document.createElement("div");
var poph4 = document.createElement("h4");
poph4.innerText = population;
popdiv.appendChild(poph4);
country.appendChild(popdiv);
var areadiv = document.createElement("div");
var areah4 = document.createElement("h4");
areah4.innerText = area;
areadiv.appendChild(areah4);
country.appendChild(areadiv);
return country;
}
f(0);
*{
transition: 500ms;
}
body {
margin: 0 15%;
min-height: 100vh;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: aliceblue;
font-family: 'Open Sans', Arial;
font-size: 18px;
}
#name {
padding: 0 20px;
background-color: rgb(219, 219, 219);
}
#capital {
padding: 0 20px;
background-color: rgb(219, 219, 219);
}
#population {
padding: 0 20px;
background-color: rgb(219, 219, 219);
}
#area {
padding: 0 20px;
background-color: rgb(219, 219, 219);
}
header {
margin: 0 10%;
display: flex;
justify-content: space-between;
padding: 22px 0;
color: rgb(5, 5, 5);
}
ul {
list-style: none;
list-style-type: none;
outline: 2px solid #ddd;
padding: 1rem 2rem;
border-radius: 0.5rem;
list-style-position: inside;
color: blue;
}
ul ol {
color: rgb(197, 105, 18);
list-style: none;
list-style-type: none;
font-size: .9em;
margin: 0.4rem 0;
}
.country {
display: flex;
justify-content: space-between;
}
.disp-flex {
display: flex;
justify-content: space-between;
}
.disp-flex>div {
width: 23%;
padding: 15px 0px;
}
.subregion-header>div:nth-child(1) {
position: relative;
left: 30px;
}
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
margin: 15px 2px;
}
.accordion li {
list-style-type: none;
}
.active,
.accordion:hover {
background-color: #ccc;
}
.panel {
margin-left: 5%;
display: none;
background-color: white;
overflow: hidden;
}
#name:hover {
background-color: rgb(114, 114, 114);
cursor: pointer;
}
#capital:hover {
background-color: rgb(114, 114, 114);
cursor: pointer;
}
#population:hover {
background-color: rgb(114, 114, 114);
cursor: pointer;
}
#area:hover {
background-color: rgb(114, 114, 114);
cursor: pointer;
}
<!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>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="container">
<header>
<div id="name">
<h1>Name</h1>
</div>
<div id="capital">
<h1>Capital</h1>
</div>
<div id="population">
<h1>Population</h1>
</div>
<div id="area">
<h1>Area</h1>
</div>
</header>
<div id="container"></div>
<div id="subRegionWrapper"> </div>
<div id="listWrapper"></div>
<script src="script.js"></script>
</main>
</body>
</html>
I made a little data-list containing some information about countries (names, capitals, areas and population), all the countries are grouped by sub-regions. In the header section I have four positions describing each column (name, capital, area and population). I want to add functionality of sorting, so that after clicking on position in the header section, the data would sort by that position. For example after clicking once on name, data should sort by name(asc or desc) and then after clicking once more on name, data should sort inversely to previous sort. I would like to allow sorting by couple columns at same time, so for example after clicking on capital and area, the data should be sorted by capitals and area. Here is my code:
I tried to do that sorting in different ways (adding extra function, passing parameter to main function etc.) but unfortunately nothing worked.I am really struggling with that, I will be extremely grateful for any help.
Your code is actually working, but you are not removing the old table. So the new sorted entries are appended to the already existing ones. You should clear the table some way, for example:
const container = document.getElementById('container');
var child = container.lastElementChild;
while (child) {
container.removeChild(child);
child = container.lastElementChild;
}
Put this where you get the container in your function f(e) and it should be working
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;
}
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>
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