Loading...
Logo
Processing Forum
All,
 
I'm working on a simple interactive chart that allows a user to rollover a datapoint to see the data detail. A simplified example of my code is below, which contains all the structural elements (big thanks to phi.lo for showing me how to do a fade-in with this!).
 
The end goal is to put this on the web with processing.js. I've been able to do this, but noticed that whether I'm running in native Processing or in processing.js the sketch bumps my cpu usage up to about 25%. I'd like to ameliorate this if possible. It seems like I should be able to do noLoop() at the end of setup, and then have some sort of function for the mouseovers that will do redraw() for as long as the mouseover condition is true But I can't figure out how to work this in the context of my code. I've tried putting the mouseover code in its own function and calling it outside of draw, but that hasn't worked.
 
Any thoughts? Many thanks!
 
Chris
 
Copy code
  1. testChart chart1, chart2;
  2.  
  3. float[] datapoint = {
  4.   1.0, 3.5, -2.0, -1.5, 6
  5. };
  6. float[] datapoint2 = {
  7.   8.0, 3.5, 2.0, 1.5, 2
  8. };
  9.  
  10. int alph = 255; //alpha value
  11. boolean over; //test for mouseover
  12.  
  13. void setup() {
  14.   size(600, 400);
  15.   smooth();

  16.   chart1 = new testChart(20, 200, 8, datapoint);
  17.   chart2 = new testChart(20, 300, 8, datapoint2);
  18. }
  19.  
  20. void draw() {
  21.   background(255);
  22.  
  23.   over = false;
  24.   chart1.drawchart();
  25.   chart2.drawchart();
  26.  
  27.   if (over) {
  28.     if (alph > 10) {
  29.       alph -= 10;
  30.     }
  31.   }
  32.   else {
  33.     alph = 255;
  34.   }
  35. }
  36.  
  37. class testChart {
  38.   int offsetX, offsetY, ellipseSize;
  39.   float[] data;
  40.  
  41.   testChart(int offsetX_, int offsetY_, int ellipseSize_, float[] data_) {
  42.     offsetX = offsetX_; //x-position
  43.     offsetY = offsetY_; //y-position
  44.     ellipseSize = ellipseSize_; //circle size
  45.     data = data_; // data array
  46.   }
  47.  
  48.   void drawchart() {
  49.     int plotX1 = offsetX;
  50.     int newY = offsetY;
  51.     stroke(5, 55, 105);
  52.     fill(164, 199, 242);
  53.  
  54.     //draw circles based on data
  55.     for (int i = 0; i < data.length; i++) {
  56.       float x = plotX1;
  57.       float y = newY -(data[i]); 
  58.       ellipse (x, y, ellipseSize, ellipseSize);
  59.       plotX1 += 25;
  60.     }
  61.     plotX1 = offsetX;
  62.  
  63.     //create pop-up windows on circle mouseover  
  64.     for (int i = 0; i < data.length; i++) {
  65.  
  66.       float x = plotX1;
  67.       float y = newY -(data[i]);
  68.       float disX = x - mouseX;
  69.       float disY = y - mouseY;
  70.       if (sqrt(sq(disX) + sq(disY)) < ellipseSize/2 ) {
  71.         over = true;
  72.         fill(5, 55, 105, alph); //this is the alpha that needs to fade in!
  73.         rect(mouseX, mouseY-10, 50, 30);
  74.       }
  75.       plotX1 += 25;
  76.     }
  77.   }
  78. }

Replies(16)

this is always a fiddle.

i suggest moving the mouseover test to a separate mouseMoved() method. and then modifying your draw to exit immediately if it's not fading in.

and reduce the frameRate to something like 15fps.

http://processing.org/reference/mouseMoved_.html
Koogy,

The framerate suggestion definitely helps. Can you get into a bit more of the specifics in your response? When you say moving the mouseover test to a separate mouseMoved() method, do you mean just taking

Copy code
  1. if (sqrt(sq(disX) + sq(disY)) < ellipseSize/2 ) {
  2.         over = true;
and putting it in mouseMoved? And how would I modify draw to exit immediately -- test for alpha = 255, and if so noLoop() ? Thanks!
The model I use is to include a noLoop() call at the end of the draw method. This means that by default, the screen is only drawn once. If you have some interaction or other process that requires the display to change, call loop() in response to that interaction. This will call the draw() method just once again since any call to draw() will also reset back to noLoop().

If you ensure that interaction is handled in the mouseMoved(), mouseDragged() and other interaction methods, these are run on separate threads to the draw() one, so should still work smoothly.

For example:

void setup()
{
  size(400,250);
  smooth();
  strokeWeight(3);
}

void draw()
{
  background(255);
  stroke(random(0,100),random(0,100),random(0,100));
  line(random(0,width),random(0,height),random(0,width),random(0,height));
  
  // Stop redrawing if there is no change to display.
  noLoop();
}

void mouseMoved()
{
  // Redraw whenever the mouse is moved near the middle of the sketch.
  if (dist(mouseX,mouseY,width/2,height/2) < 100)
  {
    loop();
  }
}


Hmm that's getting me close -- the main difference is that I need something to loop continuously when the mouse is in the test area, even if it's just resting there -- in your example draw runs once and then stops if the mouse is no longer moving.

My updated code is below. I've got separate functions for drawing the initial circles, testing for mouseover, and drawing pop-ups. I can get it to flicker when going into mouseover, but that's it. When I try to move the pop-up function into draw, nothing happens. Any suggestions? Thanks again!


testChart chart1, chart2;

float[] datapoint = {
  1.0, 3.5, -2.0, -1.5, 6
};
float[] datapoint2 = {
  8.0, 3.5, 2.0, 1.5, 2
};

int alph = 0; //alpha value
boolean over; //test for mouseover

void setup() {
  size(600, 400);
  smooth();

  chart1 = new testChart(20, 200, 8, datapoint);
  chart2 = new testChart(20, 300, 8, datapoint2);
  noLoop();
}

void draw() {
  background(255);

  over = false;
  chart1.drawchart();
  chart2.drawchart();

  if (over) {

    alph += 10;
  }
  else {
    alph = 0;
  }
    noLoop();

}

void mouseMoved() {
  chart1.mouseover();
  chart2.mouseover();
 
  chart1.drawrect();
  chart2.drawrect();

}

class testChart {
  int offsetX, offsetY, ellipseSize;
  float[] data;

  testChart(int offsetX_, int offsetY_, int ellipseSize_, float[] data_) {
    offsetX = offsetX_; //x-position
    offsetY = offsetY_; //y-position
    ellipseSize = ellipseSize_; //circle size
    data = data_; // data array
  }

  //draw circles based on data
  void drawchart() {
    int plotX1 = offsetX;
    int newY = offsetY;
    stroke(5, 55, 105);
    fill(164, 199, 242);

    for (int i = 0; i < data.length; i++) {
      float x = plotX1;
      float y = newY -(data[i]);
      ellipse (x, y, ellipseSize, ellipseSize);
      plotX1 += 25;
    }
    plotX1 = offsetX;
  }

  //draw rectangle if mouseover is true  
  void drawrect() {
    int plotX1 = offsetX;
    int newY = offsetY;
    if (over) {
      for (int i = 0; i < data.length; i++) {

        float x = plotX1;
        float y = newY -(data[i]);
        float disX = x - mouseX;
        float disY = y - mouseY;
        fill(5, 55, 105, alph); //this is the alpha that needs to fade in!
        rect(mouseX, mouseY-10, 50, 30);
      }
      plotX1 += 25;
    }
  }

  //mouseover test
  void mouseover() {
    int plotX1 = offsetX;
    int newY = offsetY;
    for (int i = 0; i < data.length; i++) {

      float x = plotX1;
      float y = newY -(data[i]);
      float disX = x - mouseX;
      float disY = y - mouseY;
      if (sqrt(sq(disX) + sq(disY)) < ellipseSize/2 ) {
        over = true;
        loop();
        //fill(5, 55, 105, alph); //this is the alpha that needs to fade in!
        // rect(mouseX, mouseY-10, 50, 30);
      }
      plotX1 += 25;
    }
  }
}

So it looks like you have two conditions, either of which would require a redraw:

1. The mouse has moved, so the rectangle needs to be redrawn at the new mouse location.

or

2. The alpha value of the rectangle has yet to reach full opacity (it's still fading in).

As I understand it, if the mouse remains stationary over a data point and the rectangle has reached full opacity, you do not want to keep redrawing since nothing about the display is changing. 

Condition 1 can be handled as my example above. Condition 2 can be handled by only setting the noLoop() at the end of draw() if alpha has reached full opacity. That should be enough to do what you want.

One of the problems with your code is that alph is never increased because draw() sets over to false every draw cycle. Even if mouse movement causes over to be set to true, by the time control is passed back to the draw loop, it gets set back to false again.

A better strategy is always to avoid setting the same same boolean variable in both the mouse and draw threads. So here is a simplified example of what I think you want to achieve using this approach:

int alph = 0;
boolean over = false;

void setup()
{
  size(400,250);
  smooth();
}

void draw()
{
  boolean doRedraw = false;
  
  background(255);  
  noFill();
  ellipse(width/2,height/2,50,50);
  
  if (over)
  {
    // Fade in rectangle if it hasn't reach full opacity.
    if (alph <250)
    {
      alph += 5;
      doRedraw = true;
    }
    fill(0,alph);
    rect(mouseX,mouseY,100,50);
  }
  
  // Stop redrawing if there is no change to display.
  if (doRedraw == false)
  {
    noLoop();
  }
}

void mouseMoved()
{
  // Make sure rectangle is drawn if mouse is in circle.
  if (dist(mouseX,mouseY,width/2,height/2) < 25)
  {
    over = true;
  }
  else
  {
    over = false;
    alph = 0;      // Reset alpha if mouse moved off circle.
  }
  
  // Redraw after every mouse move.
  loop();
}

Ah, thanks Jo. I think I understand what you did -- I'll give it a run when I'm back at work and will let you know how it turns out!
Rather than use noLoop() which causes problems detecting mouse events why don't you change the frame rate for instance use

frameRate(60);

when the display is being updated then

frameRate(5);

when the display is not being updated. The applet is still responsive to mouse and keyboard events and it reduces CPU load, althoughnot as much as noLoop(). The real advantage to this appraoch is that you don't need any complex conditional statements to swicth between the framerates because ven if you make a mistake your applet will always respond to the mouse and keyboard.

I have succesfully used this technique in an applet that enabled multiple windows to be open at the same time, each with its own draw loop and event handling.
I'm not sure I agree with you Quarks. I thought the mouse and keyboard events were on a completely different thread to the draw() thread. This is why you can still have mouse and keyboard responses while noLoop() is set. For example:
void setup()
{
  size(400,250);
  noLoop();
}

void draw()
{
  background(255);
}

void keyPressed()
{
  println(key);
}
Can you give an example of when using noLoop() gives problems detecting mouse and key events?

I agree that setting a low frame rate would (partially) overcome problems of responding to something in the draw() loop, but I think this just hides potential bugs in the event handling of a sketch rather than solves them. It's much easier to spot those bugs with a noLoop() (as demonstrated by cingraham's example).

I also don't see quite how it reduces the need for conditional statements to manage the frame rates. There will be some cases in a typical sketch when the display needs to be redrawn at full speed and some other cases when it does not need updating at all. Testing for those conditions seems necessary for any reasonably efficient sketch, regardless of whether it is to change frame rates from fast to slow or fast to noLoop().
You are right and I was wrong when I said that noLoop() prevents mouse and keyboard events.

I made the staement based on my work with the G4P library where GUI controls don't respond after a noLoop but that is because the way the library hooks into Processing.

Sorry for the confusion.

Okay Jo, I finally had a chance to test your code out. While it works in the example you gave me, the same technique doesn't work in my code. I'm using arrays and cycling through them with for loops in the various functions I have, so only the very last circle that is drawn is exhibiting the proper behavior. You can see what I'm working with below. I've tried various reworkings of your code, but no luck.

Is it possible to apply your method in this situation?


testChart chart1, chart2;

float[] datapoint = {
  1.0, 3.5, -2.0, -1.5, 6
};
float[] datapoint2 = {
  8.0, 3.5, 2.0, 1.5, 2
};

int alph = 0; //alpha value
boolean over = false;

void setup() {
  size(600, 400);
  smooth();

  chart1 = new testChart(20, 200, 8, datapoint);
  chart2 = new testChart(20, 300, 8, datapoint2);
}

void draw() {

  boolean doRedraw = false;
  background(255);

  chart1.drawchart();
  chart2.drawchart();

  if (over) {
    if (alph < 250) {
      alph += 10;
      doRedraw = true;
    }
    chart1.drawrect();
    chart2.drawrect();
  }
  if (doRedraw == false) {
    noLoop();
  }
}

void mouseMoved() {
  chart1.mouseover();
  chart2.mouseover();
  loop();
}

class testChart {
  int offsetX, offsetY, ellipseSize;
  float[] data;

  testChart(int offsetX_, int offsetY_, int ellipseSize_, float[] data_) {
    offsetX = offsetX_; //x-position
    offsetY = offsetY_; //y-position
    ellipseSize = ellipseSize_; //circle size
    data = data_; // data array
  }

  //draw circles based on data
  void drawchart() {
    int plotX1 = offsetX;
    int newY = offsetY;
    stroke(5, 55, 105);
    fill(164, 199, 242);

    for (int i = 0; i < data.length; i++) {
      float x = plotX1;
      float y = newY -(data[i]);
      ellipse (x, y, ellipseSize, ellipseSize);
      plotX1 += 25;
    }
    plotX1 = offsetX;
  }

  //draw rectangle if mouseover is true 
  void drawrect() {
    int plotX1 = offsetX;
    int newY = offsetY;
    for (int i = 0; i < data.length; i++) {
      float x = plotX1;
      float y = newY -(data[i]);
      float disX = x - mouseX;
      float disY = y - mouseY;
      fill(5, 55, 105, alph); //this is the alpha that needs to fade in!
      rect(mouseX, mouseY-10, 50, 30);
    }
    plotX1 += 25;
  }

  //mouseover test
  void mouseover() {
    int plotX1 = offsetX;
    int newY = offsetY;
    for (int i = 0; i < data.length; i++) {
      float x = plotX1;
      float y = newY -(data[i]);
      float disX = x - mouseX;
      float disY = y - mouseY;
      if (sqrt(sq(disX) + sq(disY)) < ellipseSize/2 ) {
        over = true;
      }
      else {
        over = false;
        alph = 0;
      }
      plotX1 += 25;
    } 
  }
}
sorry i never got back to you - Jo's answer covered everything i was going to suggest.

anyway i moved the "over = false;" from out of the testChart.mouseover() and into the start of mouseMoved() and that has helped.

(the way you have it it's being set to true but then being set to false by the later circles. you could, maybe, just break out of the loop when you find it. but i think the fact that you have two sets of points means that won't work.)

HOWEVER, your mouseover method (which is a terrible name, btw, as it matches a standard method) is enough to max out my processor every time i move the mouse. so that might need some work. you can get rid of the sqrt for a start*. maybe use another test that exits quicker. (and change the framerate).

* square both sides - use (x^2 + y^2) < d^2 rather than sqrt(x^2 + y^2) < d
Thanks Kooogy! That did the trick. It seems like the alpha value is being set to zero every time the mouse moves, which causes some flicker when you're already over a circle, but I think I can straighten that out.
 
Duly noted on the mousover method name.
 
Re: processing speed, I had no idea that sqrt could be a culprit. I replaced that with
 
(sq(disX) + sq(disY) < sq(ellipseSize/2) )
 
It didn't seem to make much difference for my CPU load -- does it with yours? What kind of test would exit quicker? Based on what I've read in the reference and on various websites this seems to be the standard test for a circle. What kind of system are you using, btw?
 
Thanks again.
> What kind of test would exit quicker?

hard to tell what values you're expecting for the Y offset but it looks like the x position of all the circles is restricted to the left hand side. so if the mouse is anywhere to the right of the rightmost circle (or to the left of the leftmost circle) then there's no need to do any mouseover checks against individual circles. similarly with y (although that's harder as the values aren't constant (or are they?))
Ugh, and if you have any suggestions about how to eliminate the flicker problem I'd appreciate it. I seem to have difficulty wrapping my head around proper initializing of variables...
 
 Figured this out as soon as I posted -- added an else {alph = 0;} to the if (over) statement...
Hmm, so I just turned up an issue that I think is somewhat related to the discussion of the mouseover test. When you mouseover, it's drawing a rectangle for every single circle, all stacked on top of one another. You can see this if you change mouseX and mouseY to x and y -- it'll draw a rectangle over every single circle regardless of which one you're moused over. Since the rectangles will essentially act as tooltips displaying a given circle's data, this is problematic.
 
Is there a good way to have it draw *only* the rectangle associated with the circle you're currently mousing over?
Any takers on this? I'm basically trying to do tooltips, with draw running *only* while a tooltip is being drawn so I don't throttle people's CPUs. We're really close to a solution, but what we've got above draws every single tooltip at once -- how do I get it so only the correct tooltip displays? Thanks!