Skip to content

Commit

Permalink
853 improve linebuilder (#854)
Browse files Browse the repository at this point in the history
* add more features to linebuilder

* update ugrid mapnc example script

* updated whatsnew
  • Loading branch information
veenstrajelmer committed May 16, 2024
1 parent 67792ec commit bf40b0b
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 19 deletions.
90 changes: 73 additions & 17 deletions dfm_tools/linebuilder.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,86 @@
import numpy as np
import matplotlib.pyplot as plt

__all__ = ["LineBuilder"]


class LineBuilder:
"""
https://matplotlib.org/stable/users/explain/event_handling.html
To interactively draw a line in a figure axis, for instance to use as cross-section.
- ctrl+leftmouseclick to add a point to the line
- ctrl+rightmouseclick to remove the last point of the line
- ctrl+doublemouseclick to finish and let the script continue
"""
def __init__(self, line):
#self.waiting_for_entry = True
def __init__(self, ax=None):
print("draw a line in the figure interactively: ctrl+click to add point, ctrl+rightclick to undo, ctrl+doubleclick to finish")

# get current axis if not provided
if ax is None:
ax = plt.gca()

# initialize x/y arrays and line object
self.xs = []
self.ys = []
line, = ax.plot(self.xs, self.ys,'o-') # empty line
self.line = line
self.xs = list(line.get_xdata())
self.ys = list(line.get_ydata())
self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

def __call__(self, event):
print('click', event)
# if event.inaxes!=self.line.axes:
# print('WARNING: that click was outside the figure axis')
# return
# if event.dblclick:
# print('WARNING: double click is interpreted as single click')
# return
if event.inaxes!=self.line.axes: return

# register both button press events and key press events
self.cid_button = self.line.figure.canvas.mpl_connect('button_press_event', self.on_press)
self.cid_key = self.line.figure.canvas.mpl_connect('key_press_event', self.on_press)
# start a blocking event loop to prevent continuation of the script where LineBuilder was called
self.line.figure.canvas.start_event_loop()

@property
def line_array(self):
line_array = np.c_[self.xs, self.ys]
return line_array

def add_xy_to_line(self, event):
print(f"adding point: x={event.xdata:.6f}, y={event.ydata:.6f}")
self.xs.append(event.xdata)
self.ys.append(event.ydata)
self.line_array = np.c_[self.xs, self.ys]
self.line.set_data(self.xs, self.ys)
self.line.figure.canvas.draw()

def remove_last_xy_from_line(self, event):
print("removing last point if present")
self.xs = self.xs[:-1]
self.ys = self.ys[:-1]
self.line.set_data(self.xs, self.ys)
self.line.figure.canvas.draw()

def finish_linebuilder(self):
# disconnect the interactive line drawing in the figure
self.line.figure.canvas.mpl_disconnect(self.cid_button)
self.line.figure.canvas.mpl_disconnect(self.cid_key)
# let the rest of the script where LineBuilder was called continue
self.line.figure.canvas.stop_event_loop()
print("interactive line drawing finished")

def on_press(self, event):
# do nothing if ctrl is not pressed
if event.key != "control":
return

# do nothing if no mouse button is pressed
if not hasattr(event, "button"):
# key event has no button: AttributeError: 'KeyEvent' object has no attribute 'button'
return

# do nothing if mouse click is outside axis
if event.inaxes!=self.line.axes:
print('clicks outside of the figure axis are ignored')
return

# add new point to line and wrap up line upon "ctrl + left mouse double click"
if event.dblclick:
self.finish_linebuilder()
return

# add new point to line upon "ctrl + left mouse click"
if event.button == 1:
self.add_xy_to_line(event)

# remove last point from line upon "ctrl + right mouse click"
if event.button == 3:
self.remove_last_xy_from_line(event)
3 changes: 3 additions & 0 deletions docs/whats-new.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## UNRELEASED

### Feat
- improved usability of `dfmt.LineBuilder()` in [#854](https://github.com/Deltares/dfm_tools/pull/854)


## 0.23.0 (2024-05-15)

Expand Down
5 changes: 3 additions & 2 deletions tests/examples/postprocess_mapnc_ugrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,11 @@
pc = data_frommap_merged['mesh2d_flowelem_bl'].ugrid.plot(cmap='jet') #TODO: default is edgecolor='face', should work even better with edgecolor='none', but that results in seethrough edges anyway, report to matplotlib?
pc.set_clim(clim_bl)
ax_input.set_aspect('equal')
line, = ax_input.plot([], [],'o-') # empty line
linebuilder = dfmt.LineBuilder(line) #this makes it possible to interactively click a line in the bedlevel figure. Use linebuilder.line_array as alternative line_array
# line_array is defined above, alternatively click a cross-section line_array in the figure interactively with dfmt.LineBuilder
# line_array = dfmt.LineBuilder(ax=ax_input).line_array
ax_input.plot(line_array[0,0],line_array[0,1],'bx',linewidth=3,markersize=10)
ax_input.plot(line_array[:,0],line_array[:,1],'b',linewidth=3)

fig.tight_layout()
fig.savefig(os.path.join(dir_output,f'{basename}_mesh2d_flowelem_bl'))
if crs is not None:
Expand Down

0 comments on commit bf40b0b

Please sign in to comment.