Script won't load in browser, but no error message in console

Hey folks! I'm VERY new to Processing and p5, so please be kind. :)

I'm a data journalist working on a text and sentiment analysis of the remaining three presidential candidates' speeches. I generally make visualizations in R, but I'd like to do something a bit more interactive, which is why I've turned to p5. What I'm trying to do is load a .csv file with words and their frequencies into an array. Then, I've written a constructor for how to display the words, with sizes and shades of gray determined by their frequency. I'd like the words to then radiate out from the center to the edges and then repeat.

Here is my code:

var clinton;
var words = []; // Set up array for words in file
var frequency = []; // Set up array for frequency of each word

// Load the table of Clinton's words and frequencies
function preload() {
  clinton = loadTable("cltop.csv", "header");
}

function setup() {
  createCanvas(720, 400);
  background(51);
  // Set web font to be used
  textFont("Cabin:600:latin");
  // Set 'word' and 'frequency' variables
  for (var i = 0; i < clinton.getRowCount(); i++) { // This was just getRowCount, but got an "undefined" error
    words = [];
    for(var i = 0; i < clinton.rowCount; i++) { // Same undefined error as above when using rowCount
      words[i] = clinton.get(i, 2);
      frequency = [];
      for (var i = 0; i < clinton.rowCount; i++) {
        frequency[i] = clinton.getNum(i, 3);
      }
    }

  }
  // Create link to constructor
  clStream = new Words (width/2, height/2, 14)


}

function draw() {
  clStream.move();
  clStream.display();

}

// Create constructor function
function Words(tempX, tempY, tempSize) {
  this.x = tempX;
  this.y = tempY;
  this.size = tempSize;
  this.word = words;
  this.frequency = frequency;
  this.speed = 2;

  this.move = function() {
    this.x += random(-this.speed, this.speed); // I think this is wrong, as I want the words to radiate randomly out from the center
    this.y += random(-this.speed, this.speed);
  }

  this.display = function() {
    var freqGray = map(this.frequency, 8, 52, 102, 255);
    var freqSize = map(this.frequency, 8, 52, 12, 36)
    fill(freqGray);
    noStroke();
    textSize(freqSize);
    text(this.word, 0, 0);

  };
}

I'm not entirely certain where I've gone wrong, because when I try to load it in my browser, it just says "Loading," but there is no error in the javascript console. Any advice would be appreciated. Many thanks!

Answers

  • Those nested var is look like the most likely cause to me... JS doesn't have block scope so that's equivalent to starting the setup function with:

    var i = 0;
    var i = 0;
    var i = 0;
    

    Give each nested loop a unique counter variable. The standard is i, j, k etc...

  • Also the triple nested loop itself looks suspect... The loadTables docs don't use a triple nested loop to iterate through content and the code doesn't appear to do what you intend: it looks like - assuming it works - what you'll get is frequency of characters and not words; though admittedly I haven't dug into the relevant docs; so may have misunderstood something.

    If you're interested in counting word frequencies gut instinct says use split(" ") on each line of the CSV; iterating through the resulting array and then storing word counts in an object: JS makes this particularly simple - pseudocode:

    var countedWords = {}
    
    for(word in CSVLine) {
      if(countedWords[word]) {
        countedWords[word] ++;
      }
      else {
        countedWords[word] = 1;
      }
    }
    

    Disclaimer: written in a hurry after drinking beer on a hot (by UK standards) day O:-)

  • @blindfish I think you're probably right that the triple nested loop is causing issues. The loadTable doc has three columns: ID, the words, their frequency count. I don't need to count the words, as that has already been done. I used a nested loop in order to match the second column (words) with the third (frequency). The doc does have headers, so I'm wondering if I should be using the header name instead of the column number. Like this:

    for (var i = 0; i < clinton.getRowCount(); i++) { // This was just getRowCount, but got an "undefined" error
        words = [];
        for(var j = 0; j < clinton.rowCount; j++) { // Same undefined error as above when using rowCount
          words[j] = clinton.get(j, Words); // Should header name be in quotes?
          frequency = [];
          for (var k = 0; k < clinton.rowCount; k++) {
            frequency[k] = clinton.getNum(k, Frequency);
          }
        }
    

    But I think there also may be issues with the constructor. The this.word = words and this.frequency = frequency are supposed to call the variables defined in the nested loop in setup. Have I done that correctly?

    Also, what I want is for the words to appear in the center of the sketch and then move/radiate outward, but I'm not sure how to do that. I know that what I have in my code isn't right. It's from an example in the Getting Started with p5.js book that causes a circle to jitter around the screen. I merely included it to see if I could make the words appear and move, and I was planning to try to figure out the movement pattern once it was all working.

    Btw, your initial post worked insofar as the background now loads, but the words don't. And I still don't have anything appearing in the console, so I can't tell where it's getting hung up.

  • Looking at the docs in the cold light of day things make a little more sense; but not much:

    get(): Retrieves a value from the TableRow's specified column. The column may be specified by either its ID or title.

    'ID' is ambiguous here: you're trying to use the value j to iterate over the array using its index; but it's not clear whether ID and index are one and the same...

    If I had your original csv data file it should be easy enough to debug this; but I'm not sure I want to dig too deeply into p5's underbelly: this is the sort of stuff I'd personally do in raw JS or using a dedicated data library...

  • edited May 2016

    @blindfish The reason for using p5 is that the platform I post my stories and data visualizations to accepts it and I was told that using p5 was better than using raw JS. Not sure why.

    This is what my csv file looks like when I call in in R. This is just the first 10 lines of 100 total.

    > head(read.csv("cltop.csv"), 10)
        ID     Words Frequency
    1   45    across        13
    2   99  actually         8
    3   53    always        11
    4    3   america        39
    5   30  american        16
    6   25 americans        18
    7   93    anyone         8
    8   65   believe        10
    9  100      best         8
    10  96    better         8
    

    So what I'm think I'm doing is...
    1. read in the file with loadTable
    2. pull out the individual words into one array and the frequency count for each into another array in setup(); I don't want to do anything with the ID column
    3. then the constructor function should dictate how the words appear in the sketch, with color and size determined by the associated frequency count
    4. then, when I call the constructor function in draw(), the words should first appear in the center and then radiate out from there (which I've not figured out how to do yet)

    I've been looking all over the p5 site for guidance, but it's only making me more confused. At this point, I'm so terribly confused as to whether I'm working with an array, an object, a table, etc. I appreciate your help so far, though. Thanks!

  • Answer ✓

    It looks like you were over-complicating things for yourself somewhat: you only needed a single loop to iterate over the table rows. Since you only want to extract values from two known 'columns' there's no need to iterate again: instead you just reference the columns directly. Here's some working code:

    var clinton;
    // array to store a reference to the word objects
    var words = [];
    
    //load the table of Clinton's words and frequencies
    function preload() {
      clinton = loadTable("cltop.csv", "header");
    }
    
    function setup() {
        createCanvas(720, 400);
        // iterate over the table rows
        for(var i=0; i<clinton.getRowCount(); i++){
            //- Get data out of the relevant columns for each row -//
            // From inspecting the data format it's
            // clear that where the docs say 'ID'
            // they mean 'index'; but you'd only use
            // this if you don't have a header row
            var word = clinton.get(i, "Words");
            var frequency = clinton.get(i, "Frequency");
    
            // create your Word object and add to
            // the words array for use later
            words[i] = new Word(word, frequency, width/2, height/2, 14);
    
        }
    
    }
    
    function draw() {
    
      background(255);
      // Iterate through the Word objects and run their display method.
      // Calling noStroke once here to avoid unecessary repeated function calls
      noStroke();
    
      for(var i=0; i<words.length; i++) {
          words[i].display();
      }
    
    }
    
    
    
    function Word(word, frequency, x, y, size) {
        this.x = x;
        this.y = y;
        this.size = size;
        this.word = word;
        this.frequency = frequency;
        // set a random speed
        this.vx = Math.random()*2-1;
        this.vy = Math.random()*2-1;
    }
    
    // Attach pseudo-class methods to prototype;
    // unless using the new ES2015 class syntax
    Word.prototype.display = function() {
      this.x += this.vx;  
      this.y += this.vy;
      var freqGray = map(this.frequency, 8, 52, 102, 255);
      var freqSize = map(this.frequency, 8, 52, 12, 36)
      fill(freqGray);
      textSize(freqSize);
      text(this.word, this.x, this.y);
    };
    
  • edited June 2016 Answer ✓

    // Attach pseudo-class methods to prototype;
    // unless using the new ES2015 class syntax

    @ldlpdx, here's @blindfish's sketch adapted & tweaked for ES6/ECMA2015 JS, using class: :ar!
    https://developer.Mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

    "Word Frequencies" can also be watched online at this link below: :-bd
    http://p5js.SketchPad.cc/sp/pad/view/ro.90xVTuJFcVi/latest

    cltop.csv:


    ID,Words,Frequency
    45,across,13
    99,actually,8
    53,always,11
    3,america,39
    30,american,16
    25,americans,18
    93,anyone,8
    65,believe,10
    100,best,8
    96,better,8
    

    index.html:


    <script async src=http://p5js.org/js/p5.min.js></script>
    <script async src=sketch.js></script>
    

    sketch.js:


    /**
     * Word Frequencies (v2.04)
     * by  LDLathrop & BlindFish (2016-May-15)
     * mod GoToLoop
     *
     * forum.Processing.org/two/discussion/16600/
     * script-won-t-load-in-browser-but-no-error-message-in-console#Item_8
     *
     * p5js.SketchPad.cc/sp/pad/view/ro.90xVTuJFcVi/latest
    */
    
    "use strict";
    
    const CSV_FILE  = 'cltop.csv',
          SKETCHPAD = '/static/uploaded_resources/p.19392/',
          HOSTED = false,
          CSV_PATH = (HOSTED && SKETCHPAD || '') + CSV_FILE;
    
    let candidate, words;
    
    function preload() {
      candidate = loadTable(CSV_PATH, 'header');
    }
    
    function setup() {
      createCanvas(500, 432);
      frameRate(60).noStroke().textAlign(LEFT, BASELINE);
      createWords(candidate);
    }
    
    function draw() {
      background(0);
      for (let w of words)  w.script();
    }
    
    function mousePressed() {
      for (let w of words)  w.pickSpd();
    }
    
    function createWords(csv) {
      const gap = Word.GAP + Word.MAX_SIZE;
      let minFreq = Number.POSITIVE_INFINITY, maxFreq = 0;
    
      words = Array(csv.getRowCount());
    
      csv.getRows().forEach((elem, idx) => {
        const word = elem.getString('Words'),
              freq = elem.getNum('Frequency');
    
        words[idx] = new Word(word, freq, width>>1, idx*gap + gap);
        minFreq = min(minFreq, freq), maxFreq = max(maxFreq, freq);
      });
    
      for (let w of words)  w.mapFreq(minFreq, maxFreq);
    }
    
    class Word {
      static get MIN_SPD() { return .3; }
      static get MAX_SPD() { return 3; }
    
      static get MIN_GREY() { return 0o150; }
      static get MAX_GREY() { return 0xFF; }
    
      static get MIN_SIZE() { return 12; }
      static get MAX_SIZE() { return 36; }
    
      static get GAP() { return 4; }
    
      constructor (word, freq, x, y) {
        this.word = word, this.freq = freq;
        this.x = x, this.y = y;
        this.pickSpd();
      }
    
      static negOrPos() {
        return Math.random() < .5 && -1 || 1;
      }
    
      pickSpd(spd) {
        this.spd = spd || random(Word.MIN_SPD, Word.MAX_SPD) * Word.negOrPos();
      }
    
      mapFreq(minFreq, maxFreq) {
        this.freqGray = map(this.freq, minFreq, maxFreq, Word.MIN_GREY, Word.MAX_GREY);
        this.freqSize = map(this.freq, minFreq, maxFreq, Word.MIN_SIZE, Word.MAX_SIZE);
    
        textSize(this.freqSize);
        this.w = textWidth(this.word);
      }
    
      script() {
        this.bounce();
        this.display();
      }
    
      bounce() {
        (this.x += this.spd) >= width - this.w | this.x <= 0 && (this.spd *= -1);
      }
    
      display() {
        fill(this.freqGray);
        textSize(this.freqSize);
        text(this.word, this.x, this.y);
      }
    }
    

  • @blindfish & @GoToLoop Thank you SO very much! Both of those were extremely helpful! I so appreciate all your effort in helping me untangle this. I've learned a ton from both of your answers.

  • Say, @blindfish, I have a question about these as I ended up using your solution. How could I configure 'setup' to display three 200x200 canvasses with a small margin between them in order to display the word frequency animations for the three candidates side by side? Also, if I do that, I'm not 100% certain how to change the remaining code accordingly.

    Would it be easier to do the formatting of them as side by side with 'containers' in my HTML and then embed the different sketches? If so, do you have a sense of the correct syntax?

  • Answer ✓

    @ldlpdx: probably simplest to implement with three instances of p5 and do the positioning in the HTML. IIRC you'd need to use instance mode to keep the sketch instances independent. Only downside might be a memory/performance hit running three instances.

    The alternative is building the bounds of each separate square into your Word class and having a single canvas object. Do-able but extra work...

Sign In or Register to comment.