1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
# -*- coding: utf-8 -*-
# vispy: testskip (KNOWNFAIL)
# Copyright (c) 2015, Felix Schill.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
"""
Simple demonstration of mouse drawing and editing of a line plot.
This demo extends the Line visual from scene adding mouse events that allow
modification and creation of line points with the mouse.
Vispy takes care of coordinate transforms from screen to ViewBox - the
demo works on different zoom levels.
"""
import numpy as np
from vispy import app, scene
class EditLineVisual(scene.visuals.Line):
"""
Mouse editing extension to the Line visual.
This class adds mouse picking for line points, mouse_move handling for
dragging existing points, and
adding new points when clicking into empty space.
"""
def __init__(self, *args, **kwargs):
scene.visuals.Line.__init__(self, *args, **kwargs)
self.unfreeze()
# initialize point markers
self.markers = scene.visuals.Markers(parent=self)
self.marker_colors = np.ones((len(self.pos), 4), dtype=np.float32)
self.markers.set_data(pos=self.pos, symbol="s", edge_color="red",
size=6)
self.selected_point = None
self.selected_index = -1
# snap grid size
self.gridsize = 10
self.freeze()
def on_draw(self, event):
# draw line and markers
scene.visuals.Line.draw(self)
self.markers.draw()
def print_mouse_event(self, event, what):
""" print mouse events for debugging purposes """
print('%s - pos: %r, button: %s, delta: %r' %
(what, event.pos, event.button, event.delta))
def select_point(self, pos_scene, radius=5):
"""
Get line point close to mouse pointer and its index
Parameters
----------
event : the mouse event being processed
radius : scalar
max. distance in pixels between mouse and line point to be accepted
return: (numpy.array, int)
picked point and index of the point in the pos array
"""
# project mouse radius from screen coordinates to document coordinates
mouse_radius = 6
# print("Mouse radius in document units: ", mouse_radius)
# find first point within mouse_radius
index = 0
for p in self.pos:
if np.linalg.norm(pos_scene[:3] - p) < mouse_radius:
# print p, index
# point found, return point and its index
return p, index
index += 1
# no point found, return None
return None, -1
def update_markers(self, selected_index=-1, highlight_color=(1, 0, 0, 1)):
""" update marker colors, and highlight a marker with a given color """
self.marker_colors.fill(1)
# default shape (non-highlighted)
shape = "o"
size = 6
if 0 <= selected_index < len(self.marker_colors):
self.marker_colors[selected_index] = highlight_color
# if there is a highlighted marker,
# change all marker shapes to a square
shape = "s"
size = 8
self.markers.set_data(pos=self.pos, symbol=shape, edge_color='red',
size=size, face_color=self.marker_colors)
def on_mouse_press(self, pos_scene):
# pos_scene = event.pos[:3]
# find closest point to mouse and select it
self.selected_point, self.selected_index = self.select_point(pos_scene)
# if no point was clicked add a new one
if self.selected_point is None:
print("adding point", len(self.pos))
self._pos = np.append(self.pos, [pos_scene[:3]], axis=0)
self.set_data(pos=self.pos)
self.marker_colors = np.ones((len(self.pos), 4), dtype=np.float32)
self.selected_point = self.pos[-1]
self.selected_index = len(self.pos) - 1
# update markers and highlights
self.update_markers(self.selected_index)
def on_mouse_release(self, event):
self.print_mouse_event(event, 'Mouse release')
self.selected_point = None
self.update_markers()
def on_mouse_move(self, pos_scene):
if self.selected_point is not None:
# update selected point to new position given by mouse
self.selected_point[0] = round(pos_scene[0] / self.gridsize) \
* self.gridsize
self.selected_point[1] = round(pos_scene[1] / self.gridsize) \
* self.gridsize
self.set_data(pos=self.pos)
self.update_markers(self.selected_index)
def highlight_markers(self, pos_scene):
# if no button is pressed, just highlight the marker that would be
# selected on click
hl_point, hl_index = self.select_point(pos_scene)
self.update_markers(hl_index, highlight_color=(0.5, 0.5, 1.0, 1.0))
self.update()
class Canvas(scene.SceneCanvas):
""" A simple test canvas for testing the EditLineVisual """
def __init__(self):
scene.SceneCanvas.__init__(self, keys='interactive',
size=(800, 800))
# Create some initial points
n = 7
self.unfreeze()
self.pos = np.zeros((n, 3), dtype=np.float32)
self.pos[:, 0] = np.linspace(-50, 50, n)
self.pos[:, 1] = np.random.normal(size=n, scale=10, loc=0)
# create new editable line
self.line = EditLineVisual(pos=self.pos, color='w', width=3,
antialias=True, method='gl')
self.view = self.central_widget.add_view()
self.view.camera = scene.PanZoomCamera(rect=(-100, -100, 200, 200),
aspect=1.0)
# the left mouse button pan has to be disabled in the camera, as it
# interferes with dragging line points
# Proposed change in camera: make mouse buttons configurable
self.view.camera._viewbox.events.mouse_move.disconnect(
self.view.camera.viewbox_mouse_event)
self.view.add(self.line)
self.show()
self.selected_point = None
scene.visuals.GridLines(parent=self.view.scene)
self.freeze()
def on_mouse_press(self, event):
# self.line.print_mouse_event(event, 'Mouse press')
tr = self.scene.node_transform(self.line)
pos = tr.map(event.pos)
self.line.on_mouse_press(pos)
def on_mouse_move(self, event):
# left mouse button
tr = self.scene.node_transform(self.line)
pos = tr.map(event.pos)
if event.button == 1:
self.line.on_mouse_move(pos)
else:
self.line.highlight_markers(pos)
if __name__ == '__main__':
win = Canvas()
app.run()
|