int window_size = 400; int margin_size = 15; int sz = 24; int noise_seed = 10; float noiseScale = 5.0; float min_alpha = 0.4; // so a 2d array to hold the count of character pairs. int numChars = 26; int numWords = sz; int numLetters = 20; int thisLetter = 0; int[][] count = new int[numChars][numChars]; int[] newletters = new int[numLetters]; int[][] letters = new int[numWords][numLetters]; int[] letterCount = new int[numChars]; int lastChar; int thisChar = 'z'; int currentMax = 2; float strweight = 1; float fillalpha; boolean newchar = false; boolean newword = false; float strokealpha = 1.0; void setup() { // start them all at 0; noiseSeed(noise_seed); for (int i = 0; i < numWords; i++) { for (int j = 0; j < numLetters; j++) { letters[i][j] = -1; } } for (int i = 0; i < numLetters; i++) { newletters[i] = -1; } size(window_size, window_size); colorMode(RGB, 1.0); ellipseMode(CENTER); rectMode(CENTER); background(0.3); smooth(); framerate(24); } void draw() { // read through the entire array. Draw a square for each value, // normalised by the currentMax. // if (newchar == true || newword == true) { // draw letter frequencies. // need to normalise the frequencies by something? background(0.05); translate(margin_size, margin_size); scale((window_size - 2 * margin_size) / float((numChars * sz))); // draw the character frequency histogram. if (mousePressed) { rectMode(CORNER); beginShape(LINE_STRIP); int[] sorted = reverse(sort(letterCount)); int startHeight = (numChars * sz) - sorted[0] * sz; if (startHeight < 0) startHeight = 0; // vertex(0, startHeight); for (int i = 0; i < numChars; i++) { // just draw the histogram scaling by size. noFill(); stroke(1.0, 0.0, 0.0, 0.6); int x0 = i * sz; int x1 = i * sz + sz; // float val = sorted[i]; float val = sorted[i] / float(max(numChars, sorted[0])) * float(numChars); // if (val > 0) val = log(val); int y1 = (numChars * sz) - ceil(val * sz); if (y1 < 0) y1 = 0; vertex(x0, y1); vertex(x1, y1); //line(x0, y1, x1, y1); // rect(x1 - sz/2, y1, sz, 0); } int endHeight = (numChars * sz) - sorted[25] * sz; if (sorted[0] > numChars) { float corr = float(sorted[25] * sz) / float(sorted[0] * sz); endHeight = (numChars * sz) - floor((sorted[25] * sz) * corr); } // vertex(width, endHeight); endShape(); rectMode(CENTER); // draw the bigram frequency histogram. rectMode(CORNER); beginShape(LINE_STRIP); int[] bisorted = count[0]; // append all the letter arrays to the sorted array, sorting each time. for (int i = 1; i < numChars; i++) { bisorted = concat(bisorted, count[i]); } bisorted = reverse(sort(bisorted)); int bistartHeight = (numChars * sz) - bisorted[0] * sz; if (bistartHeight < 0) bistartHeight = 0; int bi_x_step = ceil(numChars * numChars / float(bisorted.length)); vertex(0, bistartHeight); vertex(0, bistartHeight); for (int i = 0; i < bisorted.length; i++) { // just draw the histogram scaling by size. noFill(); stroke(0.0, 1.0, 1.0, 0.6); int x0 = bi_x_step * i; int x1 = bi_x_step * i + bi_x_step; // break out if it's a small window. if (x0 > numChars * sz) break; // float val = bisorted[i]; float val = bisorted[i] / float(max(numChars, bisorted[0])) * max(numChars, bisorted[0]); // if (val > 0) val = log(val); int y1 = (numChars * sz) - floor(val * sz); if (y1 < 0) y1 = 0; vertex(x0, y1); vertex(x1, y1); } int biendHeight = (numChars * sz) - bisorted[bisorted.length - 1] * sz; if (bisorted[0] > numChars) { float bicorr = float(bisorted[bisorted.length - 1] * sz) / float(bisorted[0] * sz); biendHeight = (numChars * sz) - floor((bisorted[bisorted.length - 1] * sz) * bicorr); } vertex(width, biendHeight); vertex(width, biendHeight); endShape(); rectMode(CENTER); } // end histogram // draw words. for (int i = 0; i < numWords; i++) { if (strweight < 1) strweight = 1; strokeWeight(strweight); // stroke(0.8, 0.8, 0.6, (1.0 - (i/float(numWords)))/1.5); float strokealpha = (1.0/ float(i + 1))/1.2; if (mousePressed) { strokealpha /= 3.0; } stroke(1.0, 1.0, 0.7, strokealpha); beginShape(LINE_STRIP); for (int j = 0; j < numLetters - 1; j++) { if (letters[i][j+1] == -1 && j > 0) { // repeat last vertex int x1 = letters[i][j-1] * sz + sz/2; int y1 = letters[i][j] * sz + sz/2; x1 += ceil((noise(x1, y1) - 0.5) * float(sz) * noiseScale); y1 += ceil((noise(x1, y1) - 0.5) * float(sz) * noiseScale); if (x1 < sz/2) x1 = sz/2; if (x1 > sz * numChars - sz/2) x1 = sz * numChars - sz/2; if (y1 < sz/2) y1 = sz/2; if (y1 > sz * numChars - sz/2) y1 = sz * numChars - sz/2; curveVertex(x1, y1); break; } int x1 = letters[i][j] * sz + sz/2; int y1 = letters[i][j+1] * sz + sz/2; float the_noise = make_noise(x1, y1); // wrap around. // if adding the noise brings us under 0, then we need to wrap to the right-hand size and keep moving. if (x1 + the_noise < 0) { // right-hand size less the negative portion of the transform. x1 = ceil((numChars * sz) + (x1 + the_noise)); } else if (x1 + the_noise > numChars * sz) { // 0 plus the excess x1 = floor(x1 + the_noise - (numChars * sz)); } // same for y. if (y1 + the_noise < 0) { // right-hand size less the negative portion of the transform. y1 = ceil((numChars * sz) + (y1 + the_noise)); } else if (y1 + the_noise > numChars * sz) { // 0 plus the excess y1 = floor(y1 + the_noise - (numChars * sz)); } if (j == 0) { curveVertex(x1, y1); } curveVertex(x1, y1); newword = false; } endShape(); } // draw character pairs. noStroke(); for (int i = 0; i < numChars; i++) { for (int j = 0; j < numChars; j++) { // rect(i * sz, j * sz, sz, sz); int diam = ceil((count[i][j] / float(currentMax)) * sz); int diam1 = count[i][j] * 2; //+ sz/2; if (diam1 >= sz * 2) diam1 = sz*2; int diam2 = count[j][i] + sz/2; diam2 = diam1 = diam; int x1 = i * sz + (sz/2); int y1 = j * sz + (sz/2); float the_noise = make_noise(x1, y1); // wrap around. // if adding the noise brings us under 0, then we need to wrap to the right-hand size and keep moving. if (x1 + the_noise < 0) { // right-hand size less the negative portion of the transform. x1 = ceil((numChars * sz) + (x1 + the_noise)); } else if (x1 + the_noise > numChars * sz) { // 0 plus the excess x1 = floor(x1 + the_noise - (numChars * sz)); } // same for y. if (y1 + the_noise < 0) { // right-hand size less the negative portion of the transform. y1 = ceil((numChars * sz) + (y1 + the_noise)); } else if (y1 + the_noise > numChars * sz) { // 0 plus the excess y1 = floor(y1 + the_noise - (numChars * sz)); } fillalpha = float(count[i][j]) / float(currentMax)/ 1.2; if (fillalpha < min_alpha) fillalpha = min_alpha; if (mousePressed) { fillalpha /= 3.0; } float fill_r = noise(x1, y1, count[i][j]); float fill_g = noise(y1, count[i][j], x1); float fill_b = noise(count[i][j], x1, y1); fill(1.0, fillalpha); ellipse(x1, y1, diam1, diam2); newchar = false; } } // } } float make_noise(int xa, int ya) { return ceil((noise(xa, ya) - 0.5) * float(sz) * noiseScale); } void keyPressed() { // see if the key is a letter, if (key >= 'A' && key <= 'z') { // set thisChar lastChar = thisChar; if (key <= 'Z') { thisChar = key - 'A'; } else { thisChar = key - 'a'; } // add to the list if (thisLetter < numLetters) { newletters[thisLetter++] = thisChar; } // add to the count. letterCount[thisChar] += 1; // if it's not the first case, change the array. if (lastChar != 'z') { newchar = true; count[lastChar][thisChar] += 1; if (count[lastChar][thisChar] > currentMax) { currentMax = count[lastChar][thisChar]; } } } else { // it's not a letter, so set thisChar to our magic value and // add the current word to the top of the word list. thisChar = 'z'; for (int i = (numWords - 1); i > 0; i--) { letters[i] = letters[i-1]; } letters[0] = newletters; newletters = new int[numLetters]; for (int i = 0; i < numLetters; i++) { newletters[i] = -1; } thisLetter = 0; newword = true; } }