I have a graph with a line drawn on it. I want to user to be able to change the orientation of the line by “clicking and dragging”. To do this need to implement drag-handles. Here’s how …

## Implementing Drag Line

First let me introduce the code. It is listed at the end of the last post.

The line is drawn using the **Line** function which is applied to the *FrameBox *of the graph by sending the *Add Graphics Script* message. Here is the relevant section of code:

1 2 3 4 5 6 7 8 9 10 |
// draw line: y=mx+c m = -0.4; c = 0.7; p1 = {0,c}; p2 = {1,m+c}; rep = gb << Report; fb = rep[FrameBox(1)]; fb << Add Graphics Script( Line(p1,p2) ); |

Notice that the arguments for the **Line** function are two lists, corresponding to the coordinates of the points p1 and p2 that delimit the line.

There is an alternative notation:

Line( xMatrix, yMatrix )

This notation will be more convenient, so here is a re-write of the code to exploit the x and y matrices for the two data points. I’m also taking the opportunity to express the y coordinates explicitly as a function of the x-values:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// draw line: y=mx+c m = -0.4; c = 0.7; p1x = 0; p1y = m*p1x+c; p2x = 1; p2y = m*p2x+c; xLine = Matrix({p1x,p2x}); yLine = Matrix({p1y,p2y}); rep = gb << Report; fb = rep[FrameBox(1)]; fb << Add Graphics Script( Line(xLine,yLine); ); |

I’ve done this because I want to replace **Line** with **Drag Line**, and this new function only takes matrices:

1 2 3 |
fb << Add Graphics Script( Drag Line(xLine,yLine); ); |

**Drag Line** as the name suggests, allows the user to drag the position of the line. The line can be dragged by clicking on the end points. To make this easier it helps to adjust the scale of the axes to be from -0.1 to +1.1. Here is an illustration of how the dragging works:

## Implementing Drag handles

Whilst this implements the functionality that I’m looking for, it’s not self-evident that the line is draggable. I’d like a visual cue to that effect in the form of drag handles. In which case, I don’t need the **Drag Lin**e function so I can return to using **Line**. Note: it works with the new matrix notation which will prove to be useful:

1 2 3 |
fb << Add Graphics Script( Line(xLine,yLine); ); |

I would like to have drag handles at either end of the line, but also one in the middle, to make it easy to shift the line up and down without adjusting the slope. To illustrate how to code the handles I will start with only this central handle. It is added to the graphics script by using the **Handle** function.

Handle( xPos, yPos, DragScript, MouseUpScript )

I want the handle to be plotted at the mid-point of the line. Here are the relevant new lines of code:

1 2 3 4 5 6 |
pcx = (p1x+p2x)/2; pcy = (p1y+p2y)/2; fb << Add Graphics Script( Line(xLine,yLine); Handle(pcx,pcy,DoDrag(x,y)); ); |

The variables *pcx* and *pcy* define the coordinate of the handle. When a drag operation is performed the function *DoDrag* is invoked, so I need to define this function – for now, just as a “stub”.

1 2 3 4 5 |
DoDrag = Function({x,y}, show(x,y) ); |

A drag operation is performed when the user clicks in the area of the drag handle and then moves the mouse whilst keeping the mouse button depressed.

I nearly wrote “when the handle is moved by the user the function *DoDrag *is invoked”. But this is not correct. The drag operation generates new sets of (x,y) coordinates but does not move the location of the drag handle – I need to do this within the *DoDrag* function. This video illustrates what I mean:

To move the handle I want the *DoDrag* function to update the coordinates associated with the matrix that defines the y coordinates of the line:

1 |
pcy = y; |

Where *y* is the new coordinate generated by the drag event and parsed to the *DoDrag* function as an argument. The vertical displayment *delta* of the handle can be calculated:

1 2 3 |
oldY = pcy; pcy = y; delta = pcy - oldY; |

This displacement can applied to the end-points of the line so that the line is also shifted. Here is the full definition of the *DoDrag* function:

1 2 3 4 5 6 7 8 9 10 |
Do Drag = Function({x,y}, oldY = pcy; pcy = y; delta = pcy - oldY; p1y = p1y + delta; p2y = p2y + delta; yLine = Matrix({p1y,p2y}) ); |

Here it is in action:

## Adding A Twist

The centre drag handle is convenient for shifting the line up and down. Adding drag handles to either end of the line allows the orientation of the line to be adjusted. Conveniently these handles share the same coordinates as those used to define the line.

1 2 |
Handle(p1x,p1y,DoTwist(1,x,y)); Handle(p2x,p2y,DoTwist(2,x,y)); |

The drag event invokes the function *DoTwist* and for convenience I’ve added an additional argument *pos* that will tell me which of the two handles was selected.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// draw line: y=mx+c m = -0.4; c = 0.7; p1x = 0; p1y = m*p1x+c; p2x = 1; p2y = m*p2x+c; xLine = Matrix({p1x,p2x}); yLine = Matrix({p1y,p2y}); pcx = (p1x+p2x)/2; pcy = (p1y+p2y)/2; rep = gb << Report; fb = rep[FrameBox(1)]; fb << Add Graphics Script( Line(xLine,yLine); Handle(pcx,pcy,DoDrag(x,y)); Handle(p1x,p1y,DoTwist(1,x,y)); Handle(p2x,p2y,DoTwist(2,x,y)); ); |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Do Twist = Function({pos,x,y}, If (pos==1, oldY = p1y; p1y = y; delta = oldY - p1y; p2y = p2y + delta; , oldY = p2Y; p2y = y; delta = p2y - oldY; p1y = p1y - delta; ); yLine = Matrix({p1y,p2y}) ); |

## And Finally

That completes the implementation of the grab handles, but there is one final piece of work to do, which relates to the original goal of this project:

The line is being used to define a binary response which is visualised by colouring the data points either red or green depending on their position relative to the line. I already implemented this in the previous post, but now I need to update my code to react to the drag events. I’ll take a look at that in the next post.