Hide all bezier curves except the one on mouse hover

Greetings!
I have four bezier curves (pasted code for two below as MCVE), and would like to be able to show only one if the mouse is hovered over the respective one.
I tried finding how to assign a class to the curve, use a distance condition, but all were failed attempts. I could use some guidance, so if anyone could help me out here, pretty please? thank you!

var activitesCurve = function(bzCurve) {
  bzCurve.setup = function() {
    var run = [25, 50, 60, 70];
    var walk = [15, 75, 80, 40];
    bzCurve = new BzCurve();
  }
  bzCurve.draw = function() {
    bzCurve.show();
    class BzCurve {
      constructor() {
        this.stroke = (239, 31, 193);
        this.margin = 50;
        this.height = bzCurve.windowHeight;
        this.arr = run;
        this.maxLength = run.length;
        this.pointGap = position;
      }
      show() {
        bezierCurve.stroke(this.stroke);
        bezierCurve.beginShape();
        bezierCurve.vertex(this.margin, this.height / 2 - this.arr[0]);
        for (int i = 1; i < this.maxLength; i++) {
          var posX = bezierCurve.float(i * this.pointGap + this.margin);
          var posY = bezierCurve.float(this.height / 2 - this.arr[i]);
          var c1 = bezierCurve.float((i - 1) * this.pointGap + this.margin);
          var c2 = bezierCurve.float(this.height / 2 - this.arr[i - 1]);
          var c3 = posX;
          var c4 = posY;
          bezierCurve.bezierVertex(c1, c2, c3, c4, posX, posY);
        }
        bezierCurve.endShape();
      }
    }
  }


  // Walk Line
  bezierCurve.stroke(23, 225, 0);
  bezierCurve.beginShape();
  bezierCurve.vertex(margin, height / 2 - walk[0]);
  for (int i = 1; i < walk.length; i++) {
    var posWX = bezierCurve.float(i * pointGap + margin);
    var posWY = bezierCurve.float(height / 2 - walk[i]);
    var cW1 = bezierCurve.float((i - 1) * pointGap + margin);
    var cW2 = bezierCurve.float(height / 2 - walk[i - 1]);
    var cW3 = posWX;
    var cW4 = posWY;
    bezierCurve.bezierVertex(cW1, cW2, cW3, cW4, posWX, posWY);
  }
}

First, make a class for your curves. Make a Boolean variable within the class for visibility.
Within your class, create a update function and a draw function. During the draw loop, run your update function for your curve(s) which you will have code to check the distance from the mouse; if it within a certain threshold, set the visibility variable to true (all within the update function still). Then call your draw function for that curve which you will have coded to only draw if the vis is true. This could be simplified but it is the default way I would approach it.

Btw - class for a curve would be something like this:



Class myCurve{
Boolean myVisibility;
//add variable you need to define the curve etc

//constructor below
 Void myCurve(){
//build the base curve here
}

Void update(){
//check the mouse pos vs your curve pos here
}

Void drawCurve(){
//put your drawing stuff here 
}

}
ā€˜ā€™ā€™

Hi William!

Thanks for the response.

Apologies as I messed up, Iā€™m working on P5js and not Processing :-/
Also made updates, Iā€™m working with multiple Canvases, this one named bezierCurve. The updated code is;

bezierCurve.stroke(239, 31, 193);
// Run Line
  bezierCurve.beginShape();
  bezierCurve.vertex(margin, height/2-run[0]);
  for (int i=1; i<run.length; i++) {
    var posX = bezierCurve.float(i*pointGap+margin);
    var posY = bezierCurve.float(height/2-run[i]);
    var c1 = bezierCurve.float((i-1)*pointGap+margin);
    float c2 = bezierCurve.float(height/2-run[i-1]);
    float c3 = posX;
    float c4 = posY;
    bezierCurve.bezierVertex(c1, c2, c3, c4, posX, posY);
  }
  bezierCurve.endShape();

// Walk Line
  bezierCurve.stroke(23, 225, 0);
  bezierCurve.beginShape();
  bezierCurve.vertex(margin, height/2-walk[0]);
  for (int i=1; i<walk.length; i++) {
        var posX = bezierCurve.float(i*pointGap+margin);
    var posY = bezierCurve.float(height/2-walk[i]);
    var c1 = bezierCurve.float((i-1)*pointGap+margin);
    float c2 = bezierCurve.float(height/2-walk[i-1]);
    float c3 = posX;
    float c4 = posY;
    bezierCurve.bezierVertex(c1, c2, c3, c4, posX, posY);
  }

I tried adding a class but not able to define it for some reason.

bzCurve = new BzCurve();

followed by defining the class with a nested function to display (show)

class BzCurve  {
constructor(){
...
}
show() { 
... }
}

But when I try to load it by entering

bzCurve.show();

Im getting an error;
Uncaught ReferenceError: BzCurve is not defined.

Quite lost hereā€¦

Thanks much!

Ah, on the road and have not played with p5.js too much. The only thing that I could see quickly is that there isnā€™t a constructor in your class, but this may be different in p5.js. Maybe one of the other lads will jump in with suggestions.

Thanks for letting know William, I do have a constructor included. Left it out to keep the MCVE down to M :stuck_out_tongue:
Still trying to figure this out so Iā€™m hoping a lad would chime in and help out here.
Cheers!

For each curve, enumerate the coordinates of the points that lie on it and check, using the mouse coordinate, for an overlap.

An O(n) time complexity and pixelCount space complexity solution to determine mouse hover is to create a 2D array with the dimensions of the screen and assign a number to each pixel indicating the ID of the bezier curve that overlays the given pixel (where a value of 0, or -1 could indicate that no bezier curve intersects the pixel). During runtime, lookup array[mouseX][mouseY] to determine intersection (if any) between the mouse coordinate and bezier curves.

How might you determine the coordinates of points that lie on a bezier curve?

Processing provides the following method: bezierPoint(a, b, c, d, t), whereā€¦

a	float: coordinate of first point on the curve
b	float: coordinate of first control point
c	float: coordinate of second control point
d	float: coordinate of second point on the curve
t	float: value between 0 and 1

ā€¦to enumerate the coordinates of the points that lie on each bezier curve.

Iterate over t (increments of 0.005 would seem suitable) to find coordinates of points over the entirety of each curve.

Note that for each value t, the x and y coordinates of the point are calculated separately, by feeding in solely x values of the control points to bezierPoint(), followed by another call where solely y values of control points are input. See the reference for more information.

2 Likes

Thanks for the post!

I will admit, I read and have to yet read your response multiple times to make sense of it. Seems a bit too complicated for where I am at the moment, which is stuck trying to figure out why Iā€™m not even able to define a new class for one curve on one of the two canvases.

Seems like setting up a mouseOver/ mousePressed function on a class would be the simplest way to do achieve my goal here, if only I can know why I see the error that my class is not defined when it is.

What would be the purpose to hide all but one curve?? User would have to move the mouse randomly to find other curves!

Consider all curves are pale and one curve is highlighted?

Also, you didnā€™t understand mcve.
Your mcve should be runnable so people can improve it instead of giving advice without running codeā€¦

1 Like

Thanks for the message, Chrisir!

Sure, all pale and one curve highlighted sounds good. I meant to hide all but one ā€˜only on hoverā€™ of the respective one. So curve A,B,C,D loads just fine but when user hovers over A, the others B,C and D could be hidden or even better as you suggested, pale. Thanks for clarifying.

Point noted on mcve. Iā€™ve updated the code and also pasted below. Iā€™m still debugging logic here, so kindly pardon the novice approach here. http://tiny.cc/fbisbz
If you could improve, that would be simply perfect!

var activitesCurve = function(bzCurve) {
  bzCurve.setup = function() {
    var run = [25, 50, 60, 70];
    var walk = [15, 75, 80, 40];
    bzCurve = new BzCurve();
  }
  bzCurve.draw = function() {
    bzCurve.show();
    class BzCurve {
      constructor() {
        this.stroke = (239, 31, 193);
        this.margin = 50;
        this.height = bzCurve.windowHeight;
        this.arr = run;
        this.maxLength = run.length;
        this.pointGap = position;
      }
      show() {
        bezierCurve.stroke(this.stroke);
        bezierCurve.beginShape();
        bezierCurve.vertex(this.margin, this.height / 2 - this.arr[0]);
        for (int i = 1; i < this.maxLength; i++) {
          var posX = bezierCurve.float(i * this.pointGap + this.margin);
          var posY = bezierCurve.float(this.height / 2 - this.arr[i]);
          var c1 = bezierCurve.float((i - 1) * this.pointGap + this.margin);
          var c2 = bezierCurve.float(this.height / 2 - this.arr[i - 1]);
          var c3 = posX;
          var c4 = posY;
          bezierCurve.bezierVertex(c1, c2, c3, c4, posX, posY);
        }
        bezierCurve.endShape();
      }
    }
  }


  // Walk Line
  bezierCurve.stroke(23, 225, 0);
  bezierCurve.beginShape();
  bezierCurve.vertex(margin, height / 2 - walk[0]);
  for (int i = 1; i < walk.length; i++) {
    var posWX = bezierCurve.float(i * pointGap + margin);
    var posWY = bezierCurve.float(height / 2 - walk[i]);
    var cW1 = bezierCurve.float((i - 1) * pointGap + margin);
    var cW2 = bezierCurve.float(height / 2 - walk[i - 1]);
    var cW3 = posWX;
    var cW4 = posWY;
    bezierCurve.bezierVertex(cW1, cW2, cW3, cW4, posWX, posWY);
  }
}
1 Like

We talk about an animation here because view changes depending on mouse position: which curve is highlighted.

Hence you should let your mcve have setup and draw()/loop

Yup, setup and draw now included.

1 Like

Here I worked with beziers

I hope this helps.

2 Likes

If you have four curves and they arenā€™t updating every frame, then the picking algorithm is probably faster and more flexible.

  1. create an index of colors, 1 for each stroke.
  2. create a Graphics
  3. set a thick stroke (e.g. 3-4), and draw each curve to the Graphics in a different color. update only when the curve locations change.
  4. to check what stroke you are over, get your mouse pointer pixel from the Graphics and look it up in the index.

That is going to be way, way faster than trying to compute distance. because the picking lookup is layered, it will also avoid a lot of weird corner cases with distance detection ā€“ at each intersection, some lines will be implicitly ā€œaboveā€ other lines because their pixels are drawn onto the lookup later.

Later, if you want to ovals or hexagons or rotated trapezoids or whatever, picking will handle that too ā€“ it doesnā€™t need new collision math for new entities.

2 Likes

Thanks for the your response, Jeremy!

I have SO much to learn. Could you help with resources on where to start?

  • Kindly advice what is a picking algorithm?
  1. By index of colors, did you mean an array (just being sure here)?
  2. Also, to clarify, I should try drawing all curve to the one Graphics created right?
  3. I didnā€™t understand this point. I know how to get x and y coordinates using mousex and mouseY but not sure what you meant by looking it up in the index.

Thanks a ton!

1 Like

Here is a simple demonstration. Hopefully it answers your questions.

https://editor.p5js.org/jeremydouglass/sketches/83XyPImMP

Notice that there are two kinds of drawing from the curve data:

  1. drawing the map, which is done in unique colors that are added to an index.
  2. drawing on the sketch canvas ā€“ the curves donā€™t need to be unique colors, they can all be black.

In this sketch, the picking map is displayed on the right-hand side, but normally it is just an invisible off-screen buffer ā€“ it is only shown here to help you understand how the left-hand picker is working.

The mouse position is then checked against the map to identify which curve is under the mouse, and that curve can be styled at render time.

For a full library implementation in Processing (Java) ā€“ not p5.js ā€“ see the Picking library:

http://n.clavaud.free.fr/processing/library/picking/

This enables you to generate your map as a byproduct of your drawing code, rather than the manual ā€œdrawing twiceā€ approach.

4 Likes

Hi Jeremy!

Thank you so much for illustrating with a simple and yet very helpful example. :raised_hands:

If I may, could I use this logic for data thatā€™s pulled from a JSON file? In other words, say if I would be drawing the bezier curves by first setting up a class in setup for eg,

curve1 = new BzCurve(column1);
curve2 = new BzCurve(column2);

where the JSON file would be =

[{ 
"column1": 50,
"column2": 75,
},
{
"column1": 75,
"column2": 95,
}
{
"column1": 10,
"column2": 120,
}
]

Iā€™m not sure how to replace ā€˜csā€™ in your beautiful example, and customize it.
Also, this one went straight above my head :unamused:
pg.get(mouseX, mouseY)[0]==ccols[i]

I see;

// compare the map color at mouse location
// with the index color for this object

What exactly are we getting here? Whatā€™s the meaning of map color?

I hope you donā€™t mind my followup Qs, but Iā€™m looking forward to your response :slight_smile:

Once again, thank you!

I would expect

pg.get(mouseX, mouseY)==ccols[i]
ā€¦

here.

But theoretically the idea is that pg holds the image of the same curves as the screen and the Image has the same size.
pg has the curves in distinct unique colors ccols. These colors identify each curve. Use image(pg,0,0); to see pg.

Now get() gives us the color at mouse position in pg. When we find a color in that position that matches a curve we found that the mouse is over that curve. Thatā€™s one way of picking a shape/curve.

Itā€™s a fantastic solution.

Shouldnā€™t you need 4 Positions (each with x and y and z) for each curve??

It 2 anchor points and 2 control points and each has x,y,z

Yes, this is because p5.js colors work slightly differently than Processing(Java). We are using simple grayscale index values rather than full colors:

  • save a grayscale value, like ā€œ64ā€, to ccols.
  • Use it in makePicker to draw the line, e.g. pg.stroke(64);
  • When we check with our mouse, we check the pg.get(mouseX, mouseY) pixel. In p5.js, get() ā€œReturns an array of [R,G,B,A] values for any pixelā€. So we get back [64, 64, 64, 255].
  • get()[0] gives usā€¦ 64, which matches the index value in ccols.
2 Likes