How to sort words alphabetically from a String?

In your last posted code you are using the matrix stack (with pushMatriix()) to find the coordinates of each letter as you draw it.

If you want to loop over your letters and find the next A, you need all those coordinates saved as absolute coordinates, and you need a sense of “next” – an ordered list; not a HashMap.

There are many ways to do this. One is:

class Letter {
  float x, y, r;
  char c;
  Letter(char c, float x, float y, float r) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.c = c;
  }
}

ArrayList<Letter> letters;

Now make your current draw loop code a preload for your letters data which saves a new Letter each time. You can draw at the same time, or you can later loop over letters and draw each Letter at the correct place.

Then, to draw bezier curves, loop over letters and collect a list of the indices containing the letter you want, for example “a” – you might get back an array of indices (ints) at: [1,5,17, 19, 25 etc]. Then loop over that list starting at 1, and draw a line (or curve) from Letter x,y at idx=n to Letter x,y at idx=n-1. This will draw a line from, for example, letters.get(19) to letters.get(25).

1 Like

Dear Jeremy,
Thank you very much for your precious help!
Actually I think did what you suggest in a sketch (here below) about the same poem but using world coordinates.
I will try to adapt it to my actual sketch…

PFont f;
String[] lines;
String message;
String[] sentences;
String title;
String alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ2019,.;:!?\n ";
Letter[]letters;
int x, y;
float letterSize;
float[]originPositionsX = new float [alphabet.length()];
float[]originPositionsY = new float [alphabet.length()];
float stepX= 25;
char upperCaseChar;
boolean drawText = true;
boolean drawLines=false;

void setup() {
  size(1080, 1600);
  f = createFont("Arial", 5, true);
  textFont(f);
  lines = loadStrings("Beckett.txt");  //laden des zu analysierenden textes
  message = join(lines, " ");
  lines= split(message, ",");


  letters = new Letter[message.length()];
  for (int i =0; i < message.length(); i++) {  
    letters[i]= new Letter(x*100, 200+y*5, message.charAt(i), random(20, 40));   
    x +=textWidth(message.charAt(i));
  }
}

void draw() {

  background(0);
  pushMatrix();
  pushStyle();
  fill(255, 0, 0, 50);
   translate(-150, -230);
  //rotate(80);
  textSize(420);
  textLeading (150);
  text("what\nis the\nword", x+50, y);
  popStyle(); 
  popMatrix();
  translate(20, 40);
  smooth();
  x=5;
  y=0;

  for (int i =0; i < message.length(); i++) {   
    String st=str(message.charAt(i)).toUpperCase();
    upperCaseChar= st.charAt(0);
    int index = alphabet.indexOf(upperCaseChar);
    if (index < 0) continue;

    float m = map(mouseX, 50, width-50, 0, 1);
    m=constrain(m, 0, 1);
    float sortY =index*10+5;
    float sortX =stepX;
    float interY=lerp(y, sortY, m);
    float interX=lerp(x, sortX, m);
    letters[i].y=+interY;
    letters[i].x=+interX;
    x +=textWidth(message.charAt(i));
    if (st.equals(",")) {
      y+=20;
      x=-10;
    }
    if (drawLines) {
      if (originPositionsX[index] !=0 && originPositionsY[index] !=0) {
        pushMatrix();
        translate(15, 0);
        stroke(255, 200, 0, 100);
        noFill();
        strokeWeight(1); 
        //line(originPositionsX[index], originPositionsY[index],letters[i].x, letters[i].y); 
        bezier(originPositionsX[index], originPositionsY[index], letters[i].x, letters[i].y, letters[i].x, letters[i].y+interY/7, letters[i].x, letters[i].y);
        popMatrix();
      }
      originPositionsX[index]= letters[i].x;
      originPositionsY[index]= letters[i].y;
    }
    beginShape();
    //line(letters[i].x+5, letters[i].y, letters[i].x+5, letters[i].y+interY/4);
    endShape();
  }


  for (int i=0; i < letters.length; i++) { 
    println(i);

    if (i>951) {
      letters[i].x-=15;
      letters[i].y+=30;
    }

    if ( drawText) {
      if (message.charAt(i)!=',') {
        message.replace(",", "");
        letters[i].display();
      }
    }
    if (mousePressed) {
      /* letters[i].shake();*/
    } else {
      letters[i].home();
    }
  }
}

void keyReleased() {
  if (key=='1') drawLines = !drawLines;
  if (key=='2') drawText = !drawText;
}

class Letter {

  char letter;
  float homex, homey;
  float x, y;
  float sizeText;

  Letter(float _x, float _y, char letter_, float sizeText_) {
    homex=x=_x;
    homey=y=_y;
    letter=letter_;   
    sizeText= sizeText_;
  }

  void display() {
    fill(255);
    textAlign(LEFT);
    textSize(sizeText);
    text(letter, x, y);
  }

  void shake () {
    x+=random(-2, 2);
    y+= random(-2, 2);
  }

  void home() {
    x=homex;
    y=homey;
  }
}
1 Like

Dear Jeremy,
I hardly have time to ‘code’. Here is below where I am now. I managed to create a letter class and to draw them, but I struggle with the lines between the letters, I tried with an alphabet.indexOf(upperCaseChar), but my lines don’t link all the same letters (a with all the a’s, b with all the b’s and so on). Can you tell me what is my mistake please ?!
Thank you very much in advance.
Best wishes,

L

import java.util.Map;

PFont font;

String[]lines={  
  "folly folly for to for to", 
  "what is the word", 
  "folly from this all this folly from all this", 
  "given folly given all this seeing folly seeing all this this", 
  "what is the word", 
  "this this this this here all this this here folly given all this", 
  "seeing folly seeing all this this here for to", 
  "what is the word", 
  "see glimpse seem to glimpse need to seem to glimpse folly for to need to seem to glimpse what", 
  "what is the word", 
  "and where folly for to need to seem to glimpse what where where what is the word there over there away over there", 
  "afar afar away over there", 
  "afaint afaint afar away over there what what", 
  "what is the word", 
  "seeing all this all this this all this this here folly for to see what glimpse seem to glimpse need to seem to glimpse", 
  "afaint afar away over there what", 
  "folly for to need to seem to glimpse afaint afar away over there what", 
  "what", 
  "what is the word", 
  "what is the word"
};
String alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ2019,.;:!?\n ";
// --------------------------------------------------------

String[] lines2; 
String[] c1={};
String[] wordsList2;
int Size;
float r = 40;
float angle = 0;
Letter[]letters;
String message;
char lowerCaseChar;
float interY; 
float stepX = 25;
String joinedText;
int ind;
float x, y;
// Note the HashMap's "key" is a String and "value" is an Integer
HashMap<String, Integer> hmYPos = new HashMap<String, Integer>();

//********************************************************************************************************************************************************

void setup() {
  size(1400, 1400);
  font = createFont("helvetica", 20);
  textFont(font);
  textAlign(CENTER);
  message = join(lines, ",");
  lines = split(message, ",");
  //println(message.length());
  letters = new Letter[message.length()];
  for (int i =0; i<message.length(); i++) {
    letters[i]= new Letter(x, y, message.charAt(i), r);
  }
  prepare();
}

//********************************************************************************************************************************************************

void draw() {
  background(0);
  translate (width/2, height/2);
  smooth();
  scale(0.7);
  float posX = 0; 
  float arcLength=0;
  float posY = 0;

  float[]originPositionsX = new float [alphabet.length()];
  float[]originPositionsY = new float [alphabet.length()];

  for (int j = 0; j < lines.length; j++) {

    if ( j<=lines.length) {
      r=45*(j+1);
    }

    String[]wordsInThatLine = split(lines[j], " "); 
    for (int j2 = 0; j2 < wordsInThatLine.length; j2++) {
      int index = hmYPos.get(wordsInThatLine[j2]);

      if (index<0) continue;
      arcLength+=15;
      mouseX=constrain(mouseX, width/2, width);
      float sortY = 0;
      r= map(mouseX, width/2, width, 75+45*j+1, 150+hmYPos.get(wordsInThatLine [j2])); 
      sortY=r;
      float m=map(mouseX, width/2, width, 0, 1);
      m = constrain(m, 0, 1);
      interY = lerp(posY, sortY, m);

      for (int j3 = 0; j3 < wordsInThatLine[j2].length(); j3++) {  
        char currentChar= wordsInThatLine[j2].charAt(j3);
        letters[j3]= new Letter(posX, posY, currentChar, r);
        float w=textWidth(currentChar);
        arcLength+=w+5;
        float theta =PI+arcLength/r;

        pushMatrix();
        translate(r*cos(theta), r*sin(theta)); 
        scale(0.7);
        rotate(theta+PI/2); 
        String joinedText = join(wordsInThatLine, " ");
        String st=str(joinedText.charAt(j3)).toUpperCase();
        char upperCaseChar= st.charAt(0);
        ind = alphabet.indexOf(upperCaseChar);
        if (ind < 0) continue;
        noFill();
        stroke(181, 157, 0, 250);
        strokeWeight(1);
        line(originPositionsX[ind], originPositionsY[ind], posX, interY);

        noFill();
        strokeWeight(hmYPos.get(wordsInThatLine[j2])*0.01+0.1);
        stroke(0, 50, 220, 100);
        //ellipse(0, -10, hmYPos.get(wordsInThatLine[j2])*0.07+15, hmYPos.get(wordsInThatLine[j2])*0.07+15);
        fill(255, 200);
        textAlign(CENTER, BASELINE);
        float SText = hmYPos.get(wordsInThatLine[j2])*0.035;
        textSize(SText+20);
        letters[j3].display();
        //text(currentChar, 0, 0);
        popMatrix();

        arcLength+=w/2;
      }
    }
  }
  originPositionsX[ind]= posX;
  originPositionsY[ind]= interY;
}

//------------------------------------------------------------

void prepare() {
  HashMap<String, Integer> hm1 = new HashMap<String, Integer>();
  String message;

  message= join(lines, " "); // Join all the separated strings
  lines2=split(message, ",");// Split them into sentences according to the comas
  // Now we have a copy of the whole text

  // Init 
  wordsList2 = split(join(lines, " "), " "); // We do the same than above for a new copy to initialize the HashMap
  for (String s : wordsList2) {              // We loop across the whole list of strings
    hm1.put(s.toLowerCase(), 0);             // We fill the HashMap (hm1) and put the strings to lowerCase and put all at 0
    textSize(abs(hm1.get(s)*5+Size)+15);     // We pass a textSize
  }

  // Counting 
  for (int i=0; i<lines2.length; i++) {           // We loop across the whole strings
    String word = lines2[i].toLowerCase().trim(); // We put the strings to lowerCase and get rid of comas, points, etc.
    if (hm1.get(word) != null) {                  // If the HashMap is not null
      hm1.put(word, ((int) (hm1.get(word)))+1);   // We fill the HashMap with the 2nd list of strings (word) and we add 1
    }
  }

  //// Using an enhanced loop to iterate over each entry
  for (Map.Entry me : hm1.entrySet()) {
    c1 = (String[]) append (c1, me.getKey());
  }//for
  c1=sort(c1); //We sort the strings according to the alphabet
  //println(c1);

  // simulate output and save the line number for each word in sorted position 
  int posY=0;
  for (String s : c1) {
    hmYPos.put( s, posY); 
    //Size=hmYPos.get(s);
    posY += 25;
  }
}
//---------------------------------------------------------------------------------

class Letter {

  char letter;
  float x, y, r;

  Letter(float _x, float _y, char _letter, float _r) {
    x=_x;
    y=_y;
    r=_r;
    letter=_letter;
  }
  void display() {
    fill(255);
    noStroke();
    text(letter, x, y);
  }
}//
//
1 Like

Currently you are using this to place each letter.

        pushMatrix();
        translate(r*cos(theta), r*sin(theta)); 
        scale(0.7);
        rotate(theta+PI/2); 

You can’t rotate and translate like this if you want to know each letter’s position coordinates. You have to calculate the x, y position of each letter on the canvas and then store it. Then you will have coordinates to draw connecting lines between those coordinates.

1 Like

Thank you very much for your answer @jeremydouglass!
Not sure I get it, you mean that I first need to calculate the x,y position of each letter then I can connect lines between these coordinates and after I can use

pushMatrix();
        translate(r*cos(theta), r*sin(theta)); 
        scale(0.7);
        rotate(theta+PI/2); 

in order to pass polar coordinates to letters and lines?!

No, I mean you don’t need to push, translate, scale, at all. Edit: ah, I think I see what you meant here. See below:

If you compute the absolute x, y, rotation of each letter, then you can use those to draw lines between them, and to draw them.

This is a simplified example of the wrong approach:

void draw() {
  background(192);
  translate(width/2, height/2);  
  for(int i=0; i <5; i++) {
    pushMatrix();
    rotate(i * TWO_PI/5);
    scale(0.7);
    translate(mouseX, 0);
    rect(-10,-10,20,20);
    popMatrix();
  }
}

You draw five boxes. Each time you popMatrix, the location of each box is lost. When you want to draw lines between them, you can’t – you don’t know where they are.

Now instead, consider this:

void draw() {
  background(192);
  translate(width/2, height/2);  
  ArrayList<PVector> pts = new ArrayList<PVector>();
  // compute points
  for(int i=0; i<5; i++) {
    float r = i * TWO_PI/5;
    float x = cos(r) * mouseX;
    float y = sin(r) * mouseX;
    pts.add(new PVector(x, y, r));
  }
  // draw points
  for(int i=0; i<pts.size(); i++) {
   PVector pt = pts.get(i);
   pushMatrix();
   translate(pt.x, pt.y);
   rotate(pt.z);
   scale(0.7);
   rect(-10,-10,20,20);
   popMatrix();
  }
}

The sketch draws the exact same results, but it works differently.

Now we know the x, y of each of our points. That makes it really easy to add lines connecting them – we know the points, so we can connect them.

void draw() {
  background(192);
  translate(width/2, height/2);  
  ArrayList<PVector> pts = new ArrayList<PVector>();
  // compute points
  for(int i=0; i<5; i++) {
    float r = i * TWO_PI/5;
    float x = cos(r) * mouseX;
    float y = sin(r) * mouseX;
    pts.add(new PVector(x, y, r));
  }
  // draw points
  for(int i=0; i<pts.size(); i++) {
   PVector pt = pts.get(i);
   pushMatrix();
   translate(pt.x, pt.y);
   rotate(pt.z);
   scale(0.7);
   rect(-10,-10,20,20);
   popMatrix();
  }
  
  // draw connecting lines
  for(int i=1; i<pts.size(); i++) {
    PVector pt0 = pts.get(i-1);
    PVector pt1 = pts.get(i);
    line(pt0.x, pt0.y, pt1.x, pt1.y);
  }
}

With the first approach – the one you are using, in which you translate and scale to each letter location, then throw it away – that would never work. But with the second approach, adding the lines is easy.

This can also be done with variables on a class, like letter.x, letter.y. (Currently it looks like you might have these on your Letter class but you always set them to 0). There are other ways to solve this – for example, with a function that can always return the location of a letter based on its position and other variables, rather than saving it as data. But the basic idea is the same. You need to be able to express a letter’s x,y,r explicitly, not just move the canvas matrix around.

1 Like

Thank you very very much :pray: :pray: :pray: @jeremydouglass for taking some of your precious time explaining me in details, very clear and good pedagogical approach ! Now I hope I will manage to apply this method to my sketch with letters !!

1 Like

Dear @jeremydouglass,

Thank you very much! Thanks to you some lines connect the same letters together!
Now what I’d like to achieve is switching between words located at different positions.
On the 1rst circle at the center of the spiral it would give soemthing like : “folly folly for to for to” and then I’ll switch to another poem: “folly from this all this folly” moving words to other locations…
What do you advise me to do? A switch case with the list of strings around each circle?! I hope you can help me me with this new issue. telle me if you want me to start a new topic?
Thanks a lot Jeremy.
Best wishes,

L

1 Like

Congratulations – it looks beautiful.

Do you mean that the words in the old poem will drift to their new assigned positions for words in the new poem?

Is each new poem made of only the old words rearranged, or do some words disappear and others appear?

1 Like

Thank you very much @jeremydouglass for your compliment!:pray:
No new words just the same words in new assigned positions the poems inside the poem… Thank you very much for your precious help. Best,
L

1 Like

And do you want them to drift from one set of positions to the next over a certain period of time?

1 Like

Here is a prototype to play with. When you press a key it picks new targets at random and moves the words around the screen.

/**
 * WordListDrift
 * 2020-05-21 Jeremy Douglass - Processing 3.5.4
 * discourse.processing.org/t/how-to-sort-words-alphabetically-from-a-string/12767/90
 */
String[] words;    // a list of words
PVector[] pos;     // where each word is
PVector[] targets; // where each word is going

void setup() {
  size(400, 400);
  stroke(128);
  textSize(20);
  words = new String[] {"Lorem", "ipsum", "dolor", "sit", "amet"};
  pos = randomPos(words.length);
  targets = randomPos(words.length);
}

void draw() {
  background(0);
  // targets
  fill(255, 0, 0, 64);
  for (int i=0; i<targets.length; i++) {
    ellipse(targets[i].x, targets[i].y, 20, 20);
  }
  // draw lines
  for (int i=1; i<pos.length; i++) {
    line(pos[i-1].x, pos[i-1].y, pos[i].x, pos[i].y);
  }
  // draw words
  fill(255);
  for (int i=0; i<words.length; i++) {
    // move word towards next target
    pos[i].lerp(targets[i], 0.02);
    text(words[i], pos[i].x, pos[i].y);
  }
}

void keyReleased() {
  // create new random word targets
  targets = randomPos(words.length);
}

// return a list of random positions on the canvas
PVector[] randomPos(int count) {
  PVector[] pos = new PVector[count];
  for (int i=0; i<pos.length; i++) {
    pos[i] = new PVector(random(width), random(height));
  }
  return pos;
}

Now, say you wanted to make this a word-reorderer.

  1. reorder the words and pos array – in the same way, e.g. [2, 1, 3, 0]
  2. …now the words will drift to their new targets. There is no need to change the target array.
void mouseReleased() {
  reorder(1, 2, 3, 4, 0);
}

void reorder(int... idx) {
  String[] nw = new String[words.length];
  nw[0]=words[idx[0]];
  nw[1]=words[idx[1]];
  nw[2]=words[idx[2]];
  nw[3]=words[idx[3]];
  nw[4]=words[idx[4]];
  words = nw;

  PVector[] np = new PVector[pos.length];
  np[0]=pos[idx[0]];
  np[1]=pos[idx[1]];
  np[2]=pos[idx[2]];
  np[3]=pos[idx[3]];
  np[4]=pos[idx[4]];
  pos = np;
}
1 Like

Wahoooooo!!! Thank you so much Jeremy I didn’t expect you would have answered me in such a detailed way. I will try to play around with your code to see if I completely understand it. Thank you very much for building in the jungle of the code :palm_tree::pray::pray:

1 Like

Dear @jeremydouglass,

I have done this below, but I think there might be better ways to put that…
Two questions: how can I know the number of words in each line in order to know which String to put into each line?!

        if (j2 <4) {
          j=0;
        }
        if (j2 >4 && j2 < 9) {
          j=1;
        }
        if (j2>9 && j2<18) {
          j=2;
        }

Second question: how can I use your magical lerp() ?! To go from one location to the next smoothly ?

 for (int i=0; i<words.length; i++) {
    // move word towards next target
    pos[i].lerp(targets[i], 0.02);
    text(words[i], pos[i].x, pos[i].y);

Here is the code below. Thank you very much in advance for your help!
Best wishes,
Laurent

import java.util.Map;

PFont font;

String[]lines={  
  "folly folly for to for to", 
  "what is the word", 
  "folly from this all this folly from all this"
/*"given folly given all this seeing folly seeing all this this", 
 "what is the word", 
 "this this this this here all this this here folly given all this", 
 "seeing folly seeing all this this here for to", 
 "what is the word", 
 "see glimpse seem to glimpse need to seem to glimpse folly for to need to seem to glimpse what", 
 "what is the word", 
 "and where folly for to need to seem to glimpse what where where what is the word there over there away over there", 
 "afar afar away over there", 
 "afaint afaint afar away over there what what", 
 "what is the word", 
 "seeing all this all this this all this this here folly for to see what glimpse seem to glimpse need to seem to glimpse", 
 "afaint afar away over there what", 
 "folly for to need to seem to glimpse afaint afar away over there what", 
 "what", 
 "what is the word", 
 "what is the word"*/
};
String alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ ";

String[] lines2; 
String[] c1={};
String[] wordsList2;
int Size;
float r = 45;
Letter[]letters;
//Word[]words;
String message;
String[]wordsInThatLine; 
String st;
float interY; 
PVector [] pos;
PVector [] newPos;
float[]originPositionsX = new float [alphabet.length()];
float[]originPositionsY = new float [alphabet.length()];
float posX, posY;
// Note the HashMap's "key" is a String and "value" is an Integer
HashMap<String, Integer> hmYPos = new HashMap<String, Integer>();

//********************************************************************************************************************************************************

void setup() {
  size(1400, 1400);
  font = createFont("helvetica", 20);
  textFont(font);
  textAlign(CENTER, CENTER);

  message = join(lines, ",");
  lines = split(message, ",");
  wordsList2 = split(join(lines, " "), " "); 
  letters = new Letter[message.length()];
  pos = chosenPos(message.length());
  newPos = chosenPos(message.length());

  for (int i =0; i< message.length(); i++) {
    letters[i]= new Letter(pos[i].x, pos[i].y, message.charAt(i), r);
    //posX += textWidth(message.charAt(i));
  }
  prepare();
}

//********************************************************************************************************************************************************

void draw() {
  background(0);
  translate (width/2, height/2);
  smooth();
  scale(0.7);

  float arcLength=0;
  for (int j = 0; j < lines.length; j++) {
    if ( j<=lines.length) {
      r=45*j+1;
    }

    for (int j2 = 0; j2 < wordsList2.length; j2++) {

      int index = hmYPos.get(wordsList2[j2]);
      if (index<0) continue;
      arcLength+=10;
      mouseX = constrain(mouseX, width/2, width);
      float sortY = 0;
      r= map(mouseX, width/2, width, 75+45*j+1, 150+hmYPos.get(wordsList2[j2])); 
      sortY=r;
      float m=map(mouseX, width/2, width, 0, 1);
      m = constrain(m, 0, 1);
      interY = lerp(posY, sortY, m);


      for (int j3 = 0; j3 < wordsList2[j2].length(); j3++) { 

        String st=str(wordsList2[j2].charAt(j3)).toUpperCase();
        char upperCaseChar = st.charAt(0);
        int ind = alphabet.indexOf(upperCaseChar);
        if (ind <0) continue;

        if (originPositionsX[ind] !=0 && originPositionsY[ind] !=0) {
          noFill();
          strokeWeight(1);
          stroke(255, 150, 0, 180);
          bezier(originPositionsX[ind], originPositionsY[ind], letters[j3].x+50, letters[j3].y+100, letters[j3].x-20, letters[j3].y-30, letters[j3].x, letters[j3].y);
        }
        originPositionsX[ind]= letters[j3].x;
        originPositionsY[ind]= letters[j3].y;
      }


      for (int j3 = 0; j3 < wordsList2[j2].length(); j3++) {  

        char currentChar= wordsList2[j2].charAt(j3);
        float w=textWidth(currentChar);       
        arcLength+=w/2+10;
        float theta = PI+arcLength/r;
        newPos[j2].x = cos(theta)*r;
        newPos[j2].y = sin(theta)*r;
        letters[j3] = new Letter(newPos[j2].x, newPos[j2].y, currentChar, r);
        pushMatrix();
        translate(letters[j3].x, letters[j3].y);
        rotate(theta+PI/2);
        noFill();
        strokeWeight(hmYPos.get(wordsList2[j2])*0.01+0.3);
        stroke(0, 0, 150, 150);
        ellipse(0, -40, hmYPos.get(wordsList2[j2])*0.07, hmYPos.get(wordsList2[j2])*0.07);
        fill(255, 200);
        textAlign(CENTER, CENTER);
        float SText = hmYPos.get(wordsList2[j2])*0.035;
        textSize(SText+20);
        //pos[j2].lerp(newPos[j2], 0.02);

        if (j2 <4) {
          j=0;
        }
        if (j2 >4 && j2 < 9) {
          j=1;
        }
        if (j2>9 && j2<18) {
          j=2;
        }
        text(currentChar, pos[j2].x, pos[j2].y);
        popMatrix();

        w=textWidth(currentChar);
        arcLength+=w/2;
      }
    }
  }
}

void mouseReleased() {

  reorder(0, 5, 11, 9, 17, 4, 15, 10, 1, 16, 8, 12, 3, 6, 13, 2, 14, 7, 18);
}
//---------------------------------------------------------------------------------------------------------------------------------------------


PVector[] chosenPos(int count) {
  PVector []pos = new PVector [count];
  for (int i =0; i<message.length(); i++) {
    pos[i]= new PVector(0, 0);
  }
  return pos;
}

//---------------------------------------------------------------------------------------------------------------------------------------------

void reorder(int...idx) {

  String []nw = new String [wordsList2.length];

  nw[0]= wordsList2[idx[0]];
  nw[1]=wordsList2[idx[1]];
  nw[2]=wordsList2[idx[2]];
  nw[3]=wordsList2[idx[3]];
  nw[4]=wordsList2[idx[4]];
  nw[5]=wordsList2[idx[5]];
  nw[6]=wordsList2[idx[6]];
  nw[7]=wordsList2[idx[7]];
  nw[8]=wordsList2[idx[8]];
  nw[9]=wordsList2[idx[9]];
  nw[10]=wordsList2[idx[10]];
  nw[11]=wordsList2[idx[11]];
  nw[12]=wordsList2[idx[12]];
  nw[13]=wordsList2[idx[13]];
  nw[14]=wordsList2[idx[14]];
  nw[15]=wordsList2[idx[15]];
  nw[16]=wordsList2[idx[16]];
  nw[17]=wordsList2[idx[17]];
  nw[18]=wordsList2[idx[18]];
  wordsList2 = nw;

  PVector[]yValues = new PVector[wordsList2.length];
  PVector[]wordsV = new PVector [wordsList2.length];

  yValues[0] = wordsV[idx[0]];
  yValues[1] = wordsV[idx[1]];  
  yValues[2] = wordsV[idx[2]]; 
  yValues[3] = wordsV[idx[3]];
  yValues[4] = wordsV[idx[4]];
  yValues[5] = wordsV[idx[5]];
  yValues[6] = wordsV[idx[6]];
  yValues[7] = wordsV[idx[7]];
  yValues[8] = wordsV[idx[8]];
  yValues[9] = wordsV[idx[9]];
  yValues[10] = wordsV[idx[10]];
  yValues[11] = wordsV[idx[11]];  
  yValues[12] = wordsV[idx[12]]; 
  yValues[13] = wordsV[idx[13]];
  yValues[14] = wordsV[idx[14]];
  yValues[15] = wordsV[idx[15]];
  yValues[16] = wordsV[idx[16]];
  yValues[17] = wordsV[idx[17]];
  yValues[18] = wordsV[idx[18]];
  yValues = wordsV;
}

//---------------------------------------------------------------------------------------------------------------------------------------------

void prepare() {
  HashMap<String, Integer> hm1 = new HashMap<String, Integer>();
  String message;

  message= join(lines, " "); // Join all the separated strings
  lines2=split(message, ",");// Split them into sentences according to the comas
  // Now we have a copy of the whole text

  // Init 
  wordsList2 = split(join(lines, " "), " "); 
  //println(wordsList2);// We do the same than above for a new copy to initialize the HashMap
  for (String s : wordsList2) {              // We loop across the whole list of strings
    hm1.put(s.toLowerCase(), 0);             // We fill the HashMap (hm1) and put the strings to lowerCase and put all at 0
    textSize(abs(hm1.get(s)*5+Size)+15);     // We pass a textSize
  }

  // Counting 
  for (int i=0; i<lines2.length; i++) {           // We loop across the whole strings
    String word = lines2[i].toLowerCase().trim(); // We put the strings to lowerCase and get rid of comas, points, etc.
    if (hm1.get(word) != null) {                  // If the HashMap is not null
      hm1.put(word, ((int) (hm1.get(word)))+1);   // We fill the HashMap with the 2nd list of strings (word) and we add 1
    }
  }

  //// Using an enhanced loop to iterate over each entry
  for (Map.Entry me : hm1.entrySet()) {
    c1 = (String[]) append (c1, me.getKey());
  }//for
  c1=sort(c1); //We sort the strings according to the alphabet

  // simulate output and save the line number for each word in sorted position 
  int posY=0;
  for (String s : c1) {
    hmYPos.put( s, posY); 
    //Size=hmYPos.get(s);
    posY += 25;
  }
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------------

class Letter {

  char letter;
  float x, y, r;

  Letter(float _x, float _y, char _letter, float _r) {
    x=_x;
    y=_y;
    r=_r;
    letter=_letter;
  }
  void display() {
    fill(255);
    noStroke();
    text(letter, x, y);
  }
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------------

With split and length.

String[] lines={  
  "folly folly for to for to", 
  "what is the word", 
  "folly from this all this folly from all this"
};

void setup() {
   println(lineLength(lines[0]));
   println(lineLength(lines[2]));
}

int lineLength (String s) {
  String[] words = split(s, ' ');
  return(words.length);
}

6
9

1 Like

It isn’t mine – it is just a built-in method of PVector.

Or, if you don’t want to use PVectors, you can also use the built-in lerp() on individual x and y values.

1 Like

Dear @jeremydouglass,
Thank you so so much I couldn’t find this function!
I was looking for a way to get the index of the lines and
the words length…I hope I’ll manage to think about it by myself next time ;))
It’s a great lesson for me thank you very much Jeremy,
Best,
L

Hi @jeremydouglass,
I am really sorry to ask you that but I don’t know how to use lerp to switch smoothly from one position to the other. It switches but changes strangely the letters positions, how come?! In addition I don’t know how to lerp on the bezier curves… I tried to solve it in a simpler sketch but couldn’t find any solution. I apologize for asking you some help once again :pray: :pray: :pray:
Please find the code below.
Thank you very much in advance for your help.
Best wishes,

L


import java.util.Map;

PFont font;

String[]lines={  
  "folly folly for to for to", 
  "what is the word", 
  "folly from this all this folly from all this"
/*"given folly given all this seeing folly seeing all this this", 
 "what is the word", 
 "this this this this here all this this here folly given all this", 
 "seeing folly seeing all this this here for to", 
 "what is the word", 
 "see glimpse seem to glimpse need to seem to glimpse folly for to need to seem to glimpse what", 
 "what is the word", 
 "and where folly for to need to seem to glimpse what where where what is the word there over there away over there", 
 "afar afar away over there", 
 "afaint afaint afar away over there what what", 
 "what is the word", 
 "seeing all this all this this all this this here folly for to see what glimpse seem to glimpse need to seem to glimpse", 
 "afaint afar away over there what", 
 "folly for to need to seem to glimpse afaint afar away over there what", 
 "what", 
 "what is the word", 
 "what is the word"*/
};
String alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ ";

String[] lines2; 
String[] c1={};
String[] wordsList2;
int Size;
float r = 45;
Letter[]letters;
//Word[]words;
String message;
String[]wordsInThatLine; 
String st;
float interY; 
int ind;
PVector [] pos;
PVector [] newPos;
PVector[]originPositions;
float[]originPositionsX = new float [alphabet.length()];
float[]originPositionsY = new float [alphabet.length()];
float posX, posY;
// Note the HashMap's "key" is a String and "value" is an Integer
HashMap<String, Integer> hmYPos = new HashMap<String, Integer>();

//********************************************************************************************************************************************************

void setup() {
  size(1400, 1400);
  font = createFont("helvetica", 20);
  textFont(font);
  textAlign(CENTER, CENTER);

  message = join(lines, ",");
  //println(message.length());
  lines = split(message, ",");
  wordsList2 = split(join(lines, " "), " ");
  // println(wordsList2.length);
  letters = new Letter[message.length()];
  pos = chosenPos(message.length());
  newPos = chosenPos(message.length());
  originPositions = chosenPos(message.length());
  for (int i = 0; i< message.length(); i++) {
    letters[i]= new Letter(pos[i].x, pos[i].y, message.charAt(i), r);
  }
  prepare();
}

//********************************************************************************************************************************************************

void draw() {
  background(0);
  translate (width/2, height/2);
  smooth();
  scale(0.7);

  for (int j4 = 0; j4 < wordsList2.length; j4++) {
  }


  float arcLength=0;
  for (int j = 0; j < lines.length; j++) {
    if ( j<=lines.length) {
      r=45*j+1;
    }

    for (int j2 = 0; j2 < wordsList2.length; j2++) {

      int index = hmYPos.get(wordsList2[j2]);
      if (index<0) continue;
      arcLength+=10;
      mouseX = constrain(mouseX, width/2, width);
      float sortY = 0;
      r= map(mouseX, width/2, width, 75+45*j+1, 150+hmYPos.get(wordsList2[j2])); 
      sortY=r;
      float m=map(mouseX, width/2, width, 0, 1);
      m = constrain(m, 0, 1);
      interY = lerp(posY, sortY, m);

      for (int j3 = 0; j3 < wordsList2[j2].length(); j3++) { 

        String st=str(wordsList2[j2].charAt(j3)).toUpperCase();
        char upperCaseChar = st.charAt(0);
        ind = alphabet.indexOf(upperCaseChar);
        if (ind <0) continue;
        if (originPositionsX[ind] !=0 && originPositionsY[ind] !=0) {
          noFill();
          strokeWeight(1);
          stroke(255, 150, 0, 180);
          originPositions[ind].lerp(newPos[j3], 0.02);
          bezier(originPositionsX[ind], originPositionsY[ind], newPos[j3].x+50, newPos[j3].y+100, newPos[j3].x-20, newPos[j3].y-30, newPos[j3].x, newPos[j3].y);
        }
        originPositionsX[ind] = newPos[j3].x;
        originPositionsY[ind] = newPos[j3].y;
      }


      for (int j3 = 0; j3 < wordsList2[j2].length(); j3++) {  

        if (j2 < lineLength(lines[0])-2) {
          //println(lineLength(lines[0]));
          j=0;
        }
        if (j2 > lineLength(lines[0])-2 && j2 < lineLength(lines[0]+lines[1])-1) {
          j=1;
        }
        if (j2 > lineLength(lines[0]+lines[1])-1 && j2 < lineLength(lines[0]+lines[1]+lines[2])-1) {
          j=2;
        }

        char currentChar= wordsList2[j2].charAt(j3);
        float w=textWidth(currentChar);       
        arcLength+=w/2+10;
        float theta = PI+arcLength/r;
        newPos[j3].x = cos(theta)*r;
        newPos[j3].y = sin(theta)*r;
        //letters[j3] = new Letter( newPos[j3].x, newPos[j3].y, currentChar, r);
        pushMatrix();
        translate(newPos[j3].x, newPos[j3].y);
        rotate(theta+PI/2);
        noFill();
        strokeWeight(hmYPos.get(wordsList2[j2])*0.01+0.3);
        stroke(0, 0, 150, 150);
        ellipse(0, -40, hmYPos.get(wordsList2[j2])*0.07, hmYPos.get(wordsList2[j2])*0.07);
        fill(255, 200);
        textAlign(CENTER, CENTER);
        float SText = hmYPos.get(wordsList2[j2])*0.035;
        textSize(SText+20); 
        pos[j3].lerp(newPos[j3], 0.02);
        text(currentChar, pos[j3].x, pos[j3].y);
        popMatrix();
        w=textWidth(currentChar);
        arcLength+=w/2;
      }
    }
  }
}

//---------------------------------------------------------------------------------------------------------------------------------------------

void mouseReleased() {

  reorder(0, 5, 11, 9, 17, 4, 15, 10, 1, 16, 8, 12, 3, 6, 13, 2, 14, 7, 18);
}

//---------------------------------------------------------------------------------------------------------------------------------------------


PVector[] chosenPos(int count) {
  PVector []pos = new PVector [count];
  for (int i =0; i<message.length(); i++) {
    pos[i]= new PVector(0, 0);
  }
  return pos;
}

//---------------------------------------------------------------------------------------------------------------------------------------------

int lineLength (String s) {

  String [] words = split(s, ' ');
  return words.length;
}

//---------------------------------------------------------------------------------------------------------------------------------------------

void reorder(int...idx) {

  String []nw = new String [wordsList2.length];

  nw[0]= wordsList2[idx[0]];
  nw[1]=wordsList2[idx[1]];
  nw[2]=wordsList2[idx[2]];
  nw[3]=wordsList2[idx[3]];
  nw[4]=wordsList2[idx[4]];
  nw[5]=wordsList2[idx[5]];
  nw[6]=wordsList2[idx[6]];
  nw[7]=wordsList2[idx[7]];
  nw[8]=wordsList2[idx[8]];
  nw[9]=wordsList2[idx[9]];
  nw[10]=wordsList2[idx[10]];
  nw[11]=wordsList2[idx[11]];
  nw[12]=wordsList2[idx[12]];
  nw[13]=wordsList2[idx[13]];
  nw[14]=wordsList2[idx[14]];
  nw[15]=wordsList2[idx[15]];
  nw[16]=wordsList2[idx[16]];
  nw[17]=wordsList2[idx[17]];
  nw[18]=wordsList2[idx[18]];
  wordsList2 = nw;

  PVector[]yValues = new PVector[wordsList2.length];
  PVector[]wordsV = new PVector [wordsList2.length];

  yValues[0] = wordsV[idx[0]];
  yValues[1] = wordsV[idx[1]];  
  yValues[2] = wordsV[idx[2]]; 
  yValues[3] = wordsV[idx[3]];
  yValues[4] = wordsV[idx[4]];
  yValues[5] = wordsV[idx[5]];
  yValues[6] = wordsV[idx[6]];
  yValues[7] = wordsV[idx[7]];
  yValues[8] = wordsV[idx[8]];
  yValues[9] = wordsV[idx[9]];
  yValues[10] = wordsV[idx[10]];
  yValues[11] = wordsV[idx[11]];  
  yValues[12] = wordsV[idx[12]]; 
  yValues[13] = wordsV[idx[13]];
  yValues[14] = wordsV[idx[14]];
  yValues[15] = wordsV[idx[15]];
  yValues[16] = wordsV[idx[16]];
  yValues[17] = wordsV[idx[17]];
  yValues[18] = wordsV[idx[18]];
  yValues = wordsV;
}

//---------------------------------------------------------------------------------------------------------------------------------------------

void prepare() {
  HashMap<String, Integer> hm1 = new HashMap<String, Integer>();
  String message;

  message= join(lines, " "); // Join all the separated strings
  lines2=split(message, ",");// Split them into sentences according to the comas
  // Now we have a copy of the whole text

  // Init 
  wordsList2 = split(join(lines, " "), " "); 
  //println(wordsList2);// We do the same than above for a new copy to initialize the HashMap
  for (String s : wordsList2) {              // We loop across the whole list of strings
    hm1.put(s.toLowerCase(), 0);             // We fill the HashMap (hm1) and put the strings to lowerCase and put all at 0
    textSize(abs(hm1.get(s)*5+Size)+15);     // We pass a textSize
  }

  // Counting 
  for (int i=0; i<lines2.length; i++) {           // We loop across the whole strings
    String word = lines2[i].toLowerCase().trim(); // We put the strings to lowerCase and get rid of comas, points, etc.
    if (hm1.get(word) != null) {                  // If the HashMap is not null
      hm1.put(word, ((int) (hm1.get(word)))+1);   // We fill the HashMap with the 2nd list of strings (word) and we add 1
    }
  }

  //// Using an enhanced loop to iterate over each entry
  for (Map.Entry me : hm1.entrySet()) {
    c1 = (String[]) append (c1, me.getKey());
  }//for
  c1=sort(c1); //We sort the strings according to the alphabet

  // simulate output and save the line number for each word in sorted position 
  int posY=0;
  for (String s : c1) {
    hmYPos.put( s, posY); 
    //Size=hmYPos.get(s);
    posY += 25;
  }
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------------

class Letter {

  char letter;
  float x, y, r;

  Letter(float _x, float _y, char _letter, float _r) {
    x=_x;
    y=_y;
    r=_r;
    letter=_letter;
  }
  void display() {
    fill(255);
    noStroke();
    text(letter, x, y);
  }
}
//------------------------------------------------------------------------------------------------------------------------------------------------------------------

Dear @jeremydouglass
You are certainly very busy but if you can ever help me , it would be truly great. Thanks a lot. Truly tours, L

@lolonulu – apologies, I won’t be able to look at this in any detail until early next week.

Regarding your questions:

I don’t know how to use lerp to switch smoothly from one position to the other.

What about it do you not understand? I provided an example sketch which demonstrates it working – can you say more about what is unclear?

I don’t know how to lerp on the bezier curves

lerp() changes from one value to another. PVector.lerp() changes from one point to another. So if you have a curve with many points, you could change from one set of points to another set of points.

If you are using bezier(), you could just use a float[] for the curve arguments. Here is an example.

/**
 * LerpBezierCurves
 * 2020-07-14 Jeremy Douglass
 */
int frames = 90;
float[] c1;
float[] c2;

void setup() {
  size(400,400);
  noFill();
  c1 = randomBezier(0, width, 0, height);
  c2 = randomBezier(0, width, 0, height);
}

void draw() {
  background(255);
  
  stroke(0, 255, 0);
  bezier(c1[0], c1[1], c1[2], c1[3], c1[4], c1[5], c1[6], c1[7]);

  stroke(0, 0, 255);
  bezier(c2[0], c2[1], c2[2], c2[3], c2[4], c2[5], c2[6], c2[7]);

  float amt = (frameCount%frames)/(float)frames;
  float[] lc = lerpBezier(c1, c2, amt);
  stroke(255, 0, 0);
  bezier(lc[0], lc[1], lc[2], lc[3], lc[4], lc[5], lc[6], lc[7]);
  
  if(frameCount%(2*frames)==(2*frames-1)) reset();
}

float[] randomBezier(float xmin, float xmax, float ymin, float ymax) {
  float[] curve = new float[8];
  for(int i=0; i<4; i++) {
    curve[i*2] = random(xmin, xmax);
    curve[i*2 + 1] = random(ymin, ymax);
  }  
  return curve;
}

float[] lerpBezier(float[] b1, float[] b2, float amt) {
  float[] curve = new float[8];
  for(int i=0; i<8; i++) {
    curve[i] = lerp(b1[i], b2[i], amt);
  }  
  return curve;
}

void keyPressed() {
  reset();
}

void reset() {
  frameCount = -1;
}
2 Likes