-->

p5.js code doesn't throw errors, but won't

2019-07-24 02:43发布

问题:

I'm doing an analysis of the presidential candidates speeches. I have a data file with the following variables:

> names(cl.context)
[1] "id"        "category"  "statement" "nchar"     "polarity" 

The statement variable is populated by sentences that each belong to one category from three. The polarity ranges from -1 to 1, reflecting whether the sentence has a positive bias, neutral, or negative bias.
What I'm trying to do in p5 is to have statements displayed by category, with random x,y placement, when the user clicks the mouse INSIDE the canvas. The statements themselves are colored according to their polarity.
I finally got to the point where the developer console doesn't throw any errors and it draws the canvas. But when I click within the canvas, nothing happens. No statements appear.
I'm VERY new to JavaScript, and since it's not throwing an error message, I can't resolve where the issue lies. Hoping for some advice here.

My p5 code:

var clContext;
var x;
var y;

const STATEMENTS = 118, CATEGORY = 3, QTY = STATEMENTS/CATEGORY | 0,
      POLARITY = 3,
      statements = Array(STATEMENTS), inds = Array(CATEGORY), polarity = Array(POLARITY);

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

function setup() {
  createCanvas(647, 400);
  background(51);
  // Calling noStroke once here to avoid unecessary repeated function calls
  noStroke();
  // iterate over the table rows
  for(var i=0; i<clContext.getRowCount(); i++){
      //- Get data out of the relevant columns for each row -//
      var inds = clContext.get(i, "category");
      var statements = clContext.get(i, "statement");
      var polarity = clContext.get(i, "polarity")
    }

  for (let i = 0; i < statements; randomCategoryStates(i++));
  // create your Statement object and add to
  // the statements array for use later
  inds[i] = new Statement();


    console.info(inds);
}

function draw() {
  if(mouseClicked == true){
  for(var i=0; i<inds.length; i++) {
      inds[i].display();
  }
}
}

function mouseClicked() {
  if((mouseX < width) && (mouseY < height)) {
      randomCategoryStates(~~random(CATEGORY));
      redraw();
      return false;
  }

}

// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
  let idx = inds[group], rnd;
  while ((rnd = ~~random(QTY)) == idx);
  inds[group] = rnd;
}

// Function to align statements, categories, and polarity
function Statement() {
  this.x = x;
  this.y = y;
  this.xmax = 10;
  this.ymax = 4;
  this.cat = inds;
  this.statement = statements;
  this.polarity = polarity;
  // set a random x,y position for each statement
  this.dx = (Math.random()*this.xmax) * (Math.random() < .5 ? -1 : 1);
  this.dy = (Math.random()*this.ymax) * (Math.random() < .5 ? -1 : 1);
}
// Attach pseudo-class methods to prototype;
// Maps polarity to color and x,y to random placement on canvas
Statement.prototype.display = function() {
  this.x += this.dx;
  this.y += this.dy;
  var cols = map(this.polarity == -1, 205, 38, 38);
  var cols = map(this.polarity == 0, 148, 0, 211);
  var cols = map(this.polarity == 1, 0, 145, 205);
  fill(cols);
  textSize(14);
  text(this.statement, this.x, this.y);
};  

EDIT: One thing that confused me is that the help I got with this code on the Processing forum didn't include a call for the mouseClicked() function in the draw function, so I added that. Not entirely sure that I did it correctly or if it was even necessary.

回答1:

Your code has a lot going on. I'm going to try to go through everything, in no particular order:

Why do you need these variables?

var x;
var y;

I know that you think you're using them to pass in a position to a Statement, but you never set these variables! Let's just get rid of them for now, since they aren't doing anything. This will cause an error in your code, but we'll fix that in a second.

Look at this for loop:

for(var i=0; i<clContext.getRowCount(); i++){
      //- Get data out of the relevant columns for each row -//
      var inds = clContext.get(i, "category");
      var statements = clContext.get(i, "statement");
      var polarity = clContext.get(i, "polarity")
    }

Here you're reading in the CSV file, but then you aren't doing anything with these variables. You then follow that up with this:

for (let i = 0; i < statements; randomCategoryStates(i++));
  // create your Statement object and add to
  // the statements array for use later
  inds[i] = new Statement();

Notice the semicolon after that for loop! That means the inds[i] = new Statement() line is outside the loop, which doesn't make any sense. I also don't really know what you're doing with the randomCategoryStates(i++) part.

You need to combine all of that into one loop:

  for (var i = 0; i < clContext.getRowCount(); i++) {
    var category = clContext.get(i, "category");
    var statement = clContext.get(i, "statement");
    var polarity = clContext.get(i, "polarity")
    inds[i] = new Statement();
  }

But this still doesn't make any sense, because you're never passing those variables into your Statement class. So let's take a look at that.

I'll just add some comments:

function Statement() {
  this.x = x; //when do you ever set the value of x?
  this.y = y; //when do you ever set the value of y?
  this.cat = inds; //cat is the array that holds all statements? What??
  this.statement = statements; //statement is the statements array, but nothing is ever added to that array?
  this.polarity = polarity; //when do you ever set the value of polarity?

As you can see, what you're doing here doesn't make a lot of sense. You need to change this constructor so that it takes arguments, and then you need to pass those arguments in. Something like this:

function Statement(category, polarity, statement) {
  this.category = category;
  this.statement = statement;
  this.polarity = polarity;
}

Now that we have that, we can change the line in our for loop to this:

inds[i] = new Statement(category, statement, polarity);

But that still doesn't make sense. Why do you have separate arrays for statements, categories, and polarities? Don't you just want one array that holds them all, using instances of the Statement class? So let's get rid of the inds and polarity variables, since they aren't used for anything.

We then change that line to this:

statements[i] = new Statement(category, polarity, statement);

We also have to change everywhere else that's still using the inds variable, but we have other problems while we're at it.

Let's just start with your draw() function:

function draw() {
  if (mouseClicked == true) {
    for (var i = 0; i < statements.length; i++) {
      statements[i].display();
    }
  }
}

So I guess you only want anything to display while the mouse is pressed down, and nothing to display when the mouse is not pressed down? I'm not sure that makes sense, but okay. Even so, this code doesn't make sense because mouseClicked is a function, not a variable. To determine whether the mouse is pressed, you need to use the mouseIsPressed variable, and you don't need the == true part.

if (mouseIsPressed) {

I have no idea what these two functions are supposed to do:

function mouseClicked() {
  if ((mouseX < width) && (mouseY < height)) {
    randomCategoryStates(~~random(CATEGORY));
    redraw();
    return false;
  }
}

// Function to display statements by a random category with each mouse click
function randomCategoryStates(group) {
  let idx = statements[group],
    rnd;
  while ((rnd = ~~random(QTY)) == idx);
  statements[group] = rnd;
}

There are much simpler ways to get random data. I'm just going to delete these for now, since they're more trouble than they're worth. We can go back and add the random logic later.

For now, let's look at the display() function inside your Statement class:

Statement.prototype.display = function() {
  this.x += this.dx;
  this.y += this.dy;
  var cols = map(this.polarity == -1, 205, 38, 38);
  var cols = map(this.polarity == 0, 148, 0, 211);
  var cols = map(this.polarity == 1, 0, 145, 205);
  fill(cols);
  textSize(14);
  text(this.statement, this.x, this.y);
};

We never actually declared the x, y, dx, or dy, variables, so let's add them to the constructor:

  this.x = random(width);
  this.y = random(height);
  this.dx = random(-5, 5);
  this.dy = random(-5, 5);

Back to the display() function, these lines don't make any sense:

  var cols = map(this.polarity == -1, 205, 38, 38);
  var cols = map(this.polarity == 0, 148, 0, 211);
  var cols = map(this.polarity == 1, 0, 145, 205);

Why are you declaring the same variable 3 times? Why are you trying to map a boolean value to a number value? This doesn't make any sense. For now, let's just get rid of these lines and simplify your logic:

  if(this.polarity == -1){
    fill(255, 0, 0);
  }
  else if(this.polarity == 1){
    fill(0, 255, 0);
  }
  else{
    fill(0, 0, 255);
  }

This will make negative polarity red, positive polarity green, and neutral polarity blue.

Now that we have this, we can actually run the code. When you hold the mouse down, you'll see your statements display and move around randomly. However, they'll quickly fill up your screen because you aren't ever clearing out old frames. You need to call the background() function whenever you want to clear out old frames. We might do that at the beggining of the draw() function, or right inside the if(mouseIsPressed) statement, before the for loop.

  if (mouseIsPressed) {
    background(51);
    for (var i = 0; i < statements.length; i++) {

If you make all those changes, you will have a working program. I'd be willing to bet that it still doesn't do exactly what you want. You're going to have to start much simpler. Your code is a bit of a mess, and that's a result of trying to write the whole program all at once instead of testing small pieces one at a time. This is why we ask for an MCVE, because debugging the whole thing like this is very painful. You need to start narrowing your goals down into smaller pieces.

For example, if you now want to make it so only one statement appears at a time, start over with a simpler example sketch that only shows one hardcoded statement. Get that working perfectly before you try to integrate it into your main program. If you want the statements to be ordered by category, then start over with a simpler example sketch that only shows statements based on category, without any of the extra logic. That way if you have a question about something specific, you can post that small code and it will be much easier to help you.

Good luck.