But this guy has created such a nice tree with edges and nodes and everything. And it even adds an edge and removes one when clicked on an existing node.
The idea I have in mind is to create a synonym tree with a user input word at the root. Such a tree would be different for each root word. One variation could be to create an antonym tree and compare these two.
Let’s iteratively go about this. First, lets just try to draw a circle at the bottom of the canvas. I’ll refer to https://p5js.org/reference/ for everything.
1 2 3 4 5 6 7 8 9
function setup() { createCanvas(windowWidth, windowHeight); }
function draw() { background(0); let diameter = 100 circle(windowWidth/2,windowHeight-diameter/2,diameter) }
Okay nnow I need text in the circle
1 2 3 4 5 6 7 8 9 10 11 12
function setup() { createCanvas(windowWidth, windowHeight); }
function draw() { background(0); let diameter = 100 circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(20) textAlign(CENTER,CENTER) text('Tarun',windowWidth/2,windowHeight-diameter/2) }
Now i need to create an edge
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
function setup() { createCanvas(windowWidth, windowHeight); }
function draw() { background(0); let diameter = 80 circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(20) textAlign(CENTER,CENTER) text('Tarun',windowWidth/2,windowHeight-diameter/2) let lineLength = 20 line(windowWidth/2,windowHeight-diameter,windowWidth/2,windowHeight-diameter-lineLength) stroke(255) }
But ideally i should have unlimited edges Let’s restrict number of edges to 3 (easier, can have an edge up north, one to the north-west and one to the north-east)
function setup() { createCanvas(windowWidth, windowHeight); }
function draw() { background(0); let diameter = 80 circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(20) textAlign(CENTER,CENTER) text('Tarun',windowWidth/2,windowHeight-diameter/2) let lineLength = 20 let d = diameter/(2*sqrt(2)) let l = lineLength/(sqrt(2)) line(windowWidth/2,windowHeight-diameter,windowWidth/2,windowHeight-diameter-lineLength) stroke(255) line(windowWidth/2-d,windowHeight-diameter/2-d,windowWidth/2-d-l,windowHeight-diameter/2-d-l) stroke(255) line(windowWidth/2+d,windowHeight-diameter/2-d,windowWidth/2+d+l,windowHeight-diameter/2-d-l) stroke(255) }
Now, I need to add another circle (note: horizontal is x and comes first in formulaes, vertical is y, origin is top-left)
function setup() { createCanvas(windowWidth, windowHeight); }
function draw() { background(0); let diameter = 60 circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(20) textAlign(CENTER,CENTER) text('Tarun',windowWidth/2,windowHeight-diameter/2) let lineLength = 20 let d = diameter/(2*sqrt(2)) let l = lineLength/(sqrt(2)) line(windowWidth/2,windowHeight-diameter,windowWidth/2,windowHeight-diameter-lineLength) stroke(255) line(windowWidth/2-d,windowHeight-diameter/2-d,windowWidth/2-d-l,windowHeight-diameter/2-d-l) stroke(255) line(windowWidth/2+d,windowHeight-diameter/2-d,windowWidth/2+d+l,windowHeight-diameter/2-d-l) stroke(255) newCircleCenterX1 = windowWidth/2 newCircleCenterY1 = windowHeight-diameter-lineLength-diameter/2 circle(newCircleCenterX1, newCircleCenterY1, diameter) newCircleCenterX2 = windowWidth/2-d-l-d newCircleCenterY2 = windowHeight-diameter/2-d-l-d circle(newCircleCenterX2, newCircleCenterY2, diameter) newCircleCenterX3 = windowWidth/2+d+l+d newCircleCenterY3 = windowHeight-diameter/2-d-l-d circle(newCircleCenterX3, newCircleCenterY3, diameter)
}
Now, if I could automate this process for each of the new nodes, the job would be done. so i need to abstract the method to create children out, and make it generic. For the nodes on the left, I want children to start filling from the left. For those on the right, start filling from right. I think I am lost and I have this kind of complicated code:
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
Why is it complicated? because now i have two variable: direction and numChildren. This would result in many if and elses. Maybe I should in fact write them all out before trying to escape them and getting overwhelmed.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
Now instead of ‘a’,’b’,’c’ we need to fetch actual synonyms using an api. For this purpose, we are going to use Datamuse API since it’s open and accessible.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
But wait, compiling gives an error. SyntaxError: await is only valid in async functions and the top level bodies of modules Don’t think I can make draw async. So need to abstract the main logic out.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(word){ let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = synonyms.json() drawChildren('l',windowWidth/2,windowHeight-diameter/2,3, [synonymsArray[0]['word'],synonymsArray[1]['word'], synonymsArray[2]['word']]) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms(word) //Problem: need to wait stop draw from looping noLoop(); }
But wait, now there’s an error TypeError: Cannot read properties of undefined (reading 'word') at fetchAndDrawSynonyms (/sketch.js:61:33). What does this mean? This means my code to interact with the api is not correct. I am going to try out the code as standalone in browser console
Turns out you need to do await for getting json from response as well.
Now, the api can respond with less than 3 synonyms. Need to take care of that.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(word){ let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = await synonyms.json() wordsArray = [] synonymsArray.forEach((entry) => { wordsArray.push(entry['word']) }) drawChildren('l',windowWidth/2,windowHeight-diameter/2,3, wordsArray) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms(word) //Problem: need to wait stop draw from looping noLoop(); }
But wait, I see only two lines for three nodes. I am unable to figure this out, let’s move on for now. Next step is to make the process recursive to get all the nodes. Need to invoke fetchAndDrawSynonyms from within drawChildren once centres of all children have been decided upon. But fetchAndDrawSynonyms doesn’t take centre as input, so need to make that happen.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(word,centrex, centrey){ let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = await synonyms.json() wordsArray = [] synonymsArray.forEach((entry) => { wordsArray.push(entry['word']) }) drawChildren('l',centrex, centrey,3,wordsArray) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms(word,windowWidth/2,windowHeight-diameter/2) //Problem: need to wait stop draw from looping noLoop(); }
Now, its failing at some point during recursion, probably because I havent included a stop/base condition. So lets include that. Simplest condition can be when the y coordinate of the center of some circle goes below zero, or the x-coordinate exceeds the left or right boundaries (theoretically).
Its getting a lot crowded. Probably because fetchAndDrawSynonyms is specifying ‘l’ as draw direction for every node. Let’s fix that.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(dir,word,centrex, centrey){ if(centrey <= 0 || centrex <= 0 || centrex >= windowWidth){ return } let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = await synonyms.json() wordsArray = [] synonymsArray.forEach((entry) => { wordsArray.push(entry['word']) }) drawChildren(dir,centrex, centrey,3,wordsArray) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms('l',word,windowWidth/2,windowHeight-diameter/2) //Problem: need to wait stop draw from looping noLoop(); }
It’s still pretty crowded. I see a word repeated so many times, maybe taking care of that would reduce crowding in most cases. We can keep a set for all the words already visited and remove those words from the api response.AND we need to add the new words to the set.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) let allWords = new Set() //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(dir,word,centrex, centrey){ if(centrey <= 0 || centrex <= 0 || centrex >= windowWidth){ return } let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = await synonyms.json() wordsArray = [] synonymsArray.forEach((entry) => { w = entry['word'] if(!allWords.has(w)){ wordsArray.push(w) allWords.add(w) } }) drawChildren(dir,centrex, centrey,3,wordsArray) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms('l',word,windowWidth/2,windowHeight-diameter/2) //Problem: need to wait stop draw from looping noLoop(); }
It’s somehow worse now! Also there’s an error p5.js says: text() was expecting String|Object|Array|Number|Boolean for the first parameter, received an empty variable instead. Could it be that if(variable) passes even when variable is 0? That’s not it. Oh damn, I am sending numChildren as 3 everytime irrespective of the size of children array.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) let allWords = new Set() //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(dir,word,centrex, centrey){ if(centrey <= 0 || centrex <= 0 || centrex >= windowWidth){ return } let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = await synonyms.json() wordsArray = [] synonymsArray.forEach((entry) => { w = entry['word'] if(!allWords.has(w)){ wordsArray.push(w) allWords.add(w) } }) drawChildren(dir,centrex, centrey,wordsArray.length,wordsArray) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms('l',word,windowWidth/2,windowHeight-diameter/2) //Problem: need to wait stop draw from looping noLoop(); }
Same error still exists. Ah, if no word from the api response qualifies, I should not invoke the drawChildren method at all.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) let allWords = new Set() //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function fetchAndDrawSynonyms(dir,word,centrex, centrey){ if(centrey <= 0 || centrex <= 0 || centrex >= windowWidth){ return } let synonyms = await fetch(`https://api.datamuse.com/words?rel_syn=${word}&max=3`) synonymsArray = await synonyms.json() wordsArray = [] synonymsArray.forEach((entry) => { w = entry['word'] if(!allWords.has(w)){ wordsArray.push(w) allWords.add(w) } }) if(!wordsArray.length) drawChildren(dir,centrex, centrey,wordsArray.length,wordsArray) } function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms('l',word,windowWidth/2,windowHeight-diameter/2) //Problem: need to wait stop draw from looping noLoop(); }
Now, only the root node is drawn. Apparently !wordsArray.length isn’t the same as wordsArray.length != 0 so I made that change.
Overlaps reduced but there’s a lot. Also, at some places, the word is spilling out of it’s circle. Also, the datamuse api returns confidence scores, which I should include in the graph, maybe as edge thickness. Moreover, need to take user input rather than hard-coding a word (not fun at all). Another improvement could be actually slow down the adding of nodes and show the process. To slow down, only setTimeout method is available in javascript. (As I saw in someone’s code) Custom sleep method can be put in code and used all over.
let diameter = 60 let lineLength = 20 let textsize = 20 let linecolor = 255 //p5js sqrt doesnt work outside setup/draw let d = diameter/(2*Math.sqrt(2)) let l = lineLength/(Math.sqrt(2)) let allWords = new Set() //circle is by default white, line is by default black
function setup() { createCanvas(windowWidth, windowHeight); }
async function sleep(millisec){ return new Promise( (resolve) => { setTimeout(resolve, millisec) }) }
function draw() { background(0); circle(windowWidth/2,windowHeight-diameter/2,diameter) textSize(textsize) textAlign(CENTER,CENTER) let word = 'speak' text(word,windowWidth/2,windowHeight-diameter/2) //Fetching synonyms //Should i include an await here as well? I need to, but cant make draw async //So I need to move everything else after this statement to fetchSynonyms itself // and then no need to do any await. synonyms = fetchAndDrawSynonyms('l',word,windowWidth/2,windowHeight-diameter/2) //Problem: need to wait stop draw from looping noLoop(); }
So now somehow the whole process stops after the first level. Commenting out the sleep code for now. This is how the result looks right now: