| Conditions | 26 |
| Total Lines | 253 |
| Code Lines | 169 |
| Lines | 47 |
| Ratio | 18.58 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like mutis.astro.KnotsId2dGUI() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | # Licensed under a 3-clause BSD style license - see LICENSE |
||
| 170 | def KnotsId2dGUI(mod, use_arrows=False, arrow_pos=1.0): |
||
| 171 | """ |
||
| 172 | Prompt a GUI to select identified knots and alter their label, reprenting their 2D |
||
| 173 | spatial distribution in different times. |
||
| 174 | |||
| 175 | It can be used inside jupyter notebooks, using '%matplotlib widget' first. |
||
| 176 | """ |
||
| 177 | |||
| 178 | mod = mod.copy() |
||
| 179 | |||
| 180 | knots = dict(tuple(mod.groupby('label'))) |
||
| 181 | knots_names = list(knots.keys()) |
||
| 182 | knots_values = list(knots.values()) |
||
| 183 | knots_jyears = {k:Time(knots[k]['date'].to_numpy()).jyear for k in knots} |
||
| 184 | knots_X = {k:knots[k]['X'].to_numpy() for k in knots} |
||
| 185 | knots_Y = {k:knots[k]['Y'].to_numpy() for k in knots} |
||
| 186 | |||
| 187 | |||
| 188 | from matplotlib.widgets import Slider, Button, TextBox, RectangleSelector |
||
| 189 | |||
| 190 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,8)) |
||
| 191 | |||
| 192 | lineas = list() |
||
| 193 | flechas = list() |
||
| 194 | textos = list() |
||
| 195 | |||
| 196 | def draw_all(val=2008): |
||
| 197 | nonlocal lineas, flechas, textos |
||
| 198 | |||
| 199 | # instead of clearing the whole axis, remove artists |
||
| 200 | for linea in lineas: |
||
| 201 | if linea is not None: |
||
| 202 | linea.remove() |
||
| 203 | for texto in textos: |
||
| 204 | if texto is not None: |
||
| 205 | texto.remove() |
||
| 206 | for flecha in flechas: |
||
| 207 | if flecha is not None: |
||
| 208 | flecha.remove() |
||
| 209 | ax.set_prop_cycle(None) |
||
| 210 | |||
| 211 | lineas = list() |
||
| 212 | flechas = list() |
||
| 213 | textos = list() |
||
| 214 | |||
| 215 | xlim, ylim = ax.get_xlim(), ax.get_ylim() |
||
| 216 | |||
| 217 | #ax.clear() # either clear the whole axis or remove every artist separetly |
||
| 218 | |||
| 219 | for i, label in enumerate(knots_names): |
||
| 220 | years = knots_jyears[label] |
||
| 221 | idx = (val-1.5 < years) & (years < val) |
||
| 222 | x = knots_X[label][idx] |
||
| 223 | y = knots_Y[label][idx] |
||
| 224 | |||
| 225 | lineas.append(ax.plot(x, y, '.-', linewidth=0.6, alpha=0.4, label=label)[0]) |
||
| 226 | |||
| 227 | if use_arrows: |
||
| 228 | if len(x) > 1: |
||
| 229 | flechas.append(ax.quiver(x[:-1], |
||
| 230 | y[:-1], |
||
| 231 | arrow_pos*(x[1:] - x[:-1]), |
||
| 232 | arrow_pos*(y[1:] - y[:-1]), |
||
| 233 | scale_units='xy', angles='xy', scale=1, |
||
| 234 | width=0.0015, headwidth=10, headlength=10, headaxislength=6, |
||
| 235 | alpha=0.5, color=lineas[i].get_color())) |
||
| 236 | else: |
||
| 237 | flechas.append(None) |
||
| 238 | |||
| 239 | if len(x) > 0: |
||
| 240 | #textos.append(ax.annotate(label, (x[0], y[0]), (-28,-10), textcoords='offset points', color=lineas[i].get_color(), fontsize=14)) |
||
| 241 | textos.append(ax.text(x[-1]+0.015, y[-1]+0.015, label, {'color':lineas[i].get_color(), 'fontsize':14})) |
||
| 242 | else: |
||
| 243 | textos.append(None) |
||
| 244 | |||
| 245 | ax.set_xlim(xlim) |
||
| 246 | ax.set_ylim(ylim) |
||
| 247 | ax.set_aspect('equal') |
||
| 248 | |||
| 249 | fig.canvas.draw_idle() # if removed every artist separately instead of ax.clear() |
||
| 250 | |||
| 251 | draw_all() |
||
| 252 | |||
| 253 | def update(val): |
||
| 254 | nonlocal lineas, flechas, textos |
||
| 255 | |||
| 256 | for i, label in enumerate(knots_names): |
||
| 257 | years = knots_jyears[label] |
||
| 258 | idx = (val-1.5 < years) & (years < val) |
||
| 259 | x = knots_X[label][idx] |
||
| 260 | y = knots_Y[label][idx] |
||
| 261 | |||
| 262 | lineas[i].set_xdata(x) |
||
| 263 | lineas[i].set_ydata(y) |
||
| 264 | |||
| 265 | if textos[i] is not None: |
||
| 266 | if len(x) > 0: |
||
| 267 | textos[i].set_position((x[-1]+0.015, y[-1]+0.015)) |
||
| 268 | textos[i].set_text(label) |
||
| 269 | else: |
||
| 270 | textos[i].remove() |
||
| 271 | textos[i] = None |
||
| 272 | #textos[i].set_position((10, 10)) |
||
| 273 | else: |
||
| 274 | if len(x) > 0: |
||
| 275 | textos[i] = ax.text(x[-1]+0.02, y[-1]+0.02, label, {'color':lineas[i].get_color(), 'fontsize':14}) |
||
| 276 | |||
| 277 | if use_arrows: |
||
| 278 | if flechas[i] is not None: |
||
| 279 | flechas[i].remove() |
||
| 280 | flechas[i] = None |
||
| 281 | |||
| 282 | flechas[i] = ax.quiver(x[:-1], |
||
| 283 | y[:-1], |
||
| 284 | arrow_pos*(x[1:] - x[:-1]), |
||
| 285 | arrow_pos*(y[1:] - y[:-1]), |
||
| 286 | scale_units='xy', angles='xy', scale=1, |
||
| 287 | width=0.0015, headwidth=10, headlength=10, headaxislength=6, |
||
| 288 | alpha=0.5, color=lineas[i].get_color()) |
||
| 289 | |||
| 290 | fig.canvas.draw_idle() |
||
| 291 | |||
| 292 | |||
| 293 | |||
| 294 | selected_knot = None |
||
| 295 | selected_ind = None |
||
| 296 | selected_x = None |
||
| 297 | selected_y = None |
||
| 298 | |||
| 299 | |||
| 300 | View Code Duplication | def submit_textbox(text): |
|
| 301 | nonlocal mod, knots, knots_names, knots_values, knots_jyears, knots_X, knots_Y |
||
| 302 | |||
| 303 | log.debug('Submited with:') |
||
| 304 | log.debug(f' selected_knot {selected_knot}') |
||
| 305 | log.debug(f' selected_ind {selected_ind}') |
||
| 306 | log.debug(f' selected_x {selected_x}') |
||
| 307 | log.debug(f' selected_y {selected_y}') |
||
| 308 | |||
| 309 | if selected_knot is not None: |
||
| 310 | mod.loc[selected_ind, 'label'] = text.upper() |
||
| 311 | |||
| 312 | knots = dict(tuple(mod.groupby('label'))) |
||
| 313 | knots_names = list(knots.keys()) |
||
| 314 | knots_values = list(knots.values()) |
||
| 315 | knots_jyears = {k:Time(knots[k]['date'].to_numpy()).jyear for k in knots} |
||
| 316 | knots_X = {k:knots[k]['X'].to_numpy() for k in knots} |
||
| 317 | knots_Y = {k:knots[k]['Y'].to_numpy() for k in knots} |
||
| 318 | |||
| 319 | print(f"Updated index {selected_ind} to {text.upper()}") |
||
| 320 | else: |
||
| 321 | pass |
||
| 322 | |||
| 323 | draw_all(slider_date.val) |
||
| 324 | |||
| 325 | def line_select_callback(eclick, erelease): |
||
| 326 | nonlocal selected_knot,selected_x, selected_y, selected_ind |
||
| 327 | |||
| 328 | # 1 eclick and erelease are the press and release events |
||
| 329 | x1, y1 = eclick.xdata, eclick.ydata |
||
| 330 | x2, y2 = erelease.xdata, erelease.ydata |
||
| 331 | log.debug("GUI: (%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2)) |
||
| 332 | log.debug("GUI: The button you used were: %s %s" % (eclick.button, erelease.button)) |
||
| 333 | |||
| 334 | selected_knot = None |
||
| 335 | selected_x = None |
||
| 336 | selected_y = None |
||
| 337 | selected_ind = None |
||
| 338 | |||
| 339 | for i, label in enumerate(knots_names): |
||
| 340 | years = knots_jyears[label] |
||
| 341 | idx = (slider_date.val-1.5 < years) & (years < slider_date.val) |
||
| 342 | |||
| 343 | if np.sum(idx) == 0: |
||
| 344 | continue # we did not select any component from this component, next one |
||
| 345 | |||
| 346 | x = np.array(knots_X[label]) |
||
| 347 | y = np.array(knots_Y[label]) |
||
| 348 | |||
| 349 | # get points iside current rectangle for current date |
||
| 350 | rect_idx = (x1 < x) & ( x < x2) & (y1 < y) & ( y < y2) & idx |
||
| 351 | |||
| 352 | View Code Duplication | if np.sum(rect_idx) > 0: |
|
| 353 | textbox.set_val(label) |
||
| 354 | selected_knot = label |
||
| 355 | selected_x = x[rect_idx].ravel() |
||
| 356 | selected_y = y[rect_idx].ravel() |
||
| 357 | selected_ind = knots[label].index[rect_idx] |
||
| 358 | log.debug(f'Selected {label} points rect_idx {rect_idx} x {x[rect_idx]}, y {y[rect_idx]} with indices {selected_ind}') |
||
| 359 | textbox.begin_typing(None) |
||
| 360 | break # if we find selected components in this epoch, continue with renaming |
||
| 361 | else: |
||
| 362 | pass |
||
| 363 | |||
| 364 | update(slider_date.val) |
||
| 365 | |||
| 366 | |||
| 367 | View Code Duplication | def toggle_selector(event): |
|
| 368 | log.debug('GUI: Key pressed.', end=' ') |
||
| 369 | if event.key in ['Q', 'q'] and toggle_selector.RS.active: |
||
| 370 | log.debug('Selector deactivated.') |
||
| 371 | toggle_selector.RS.set_active(False) |
||
| 372 | if event.key in ['S', 's'] and not toggle_selector.RS.active: |
||
| 373 | log.debug('Selector activated.') |
||
| 374 | toggle_selector.RS.set_active(True) |
||
| 375 | if event.key in ['R', 'r']: |
||
| 376 | log.debug('Selector deactivated.') |
||
| 377 | toggle_selector.RS.set_active(False) |
||
| 378 | textbox.begin_typing(None) |
||
| 379 | #textbox.set_val('') |
||
| 380 | |||
| 381 | |||
| 382 | toggle_selector.RS = RectangleSelector(ax, line_select_callback, |
||
| 383 | drawtype='box', useblit=True, |
||
| 384 | button=[1, 3], # don't use middle button |
||
| 385 | minspanx=0, minspany=0, |
||
| 386 | spancoords='data', |
||
| 387 | interactive=False) |
||
| 388 | |||
| 389 | |||
| 390 | #plt.connect('key_press_event', toggle_selector) |
||
| 391 | fig.canvas.mpl_connect('key_press_event', toggle_selector) |
||
| 392 | |||
| 393 | |||
| 394 | |||
| 395 | from mpl_toolkits.axes_grid1 import make_axes_locatable |
||
| 396 | |||
| 397 | divider_slider = make_axes_locatable(ax) |
||
| 398 | slider_ax = divider_slider.append_axes("top", size="3%", pad="4%") |
||
| 399 | slider_date = Slider(ax=slider_ax, label="Date", valmin=2007, valmax=2020, valinit=2008, valstep=0.2, orientation="horizontal") |
||
| 400 | slider_date.on_changed(update) |
||
| 401 | |||
| 402 | #divider_textbox = make_axes_locatable(ax) |
||
| 403 | #textbox_ax = divider_textbox.append_axes("bottom", size="3%", pad="4%") |
||
| 404 | textbox_ax = fig.add_axes([0.3,0.015,0.5,0.05]) |
||
| 405 | textbox = TextBox(textbox_ax, 'Knot name:', initial='None') |
||
| 406 | textbox.on_submit(submit_textbox) |
||
| 407 | |||
| 408 | |||
| 409 | |||
| 410 | ax.set_xlim([-1.0, +1.0]) |
||
| 411 | ax.set_ylim([-1.0, +1.0]) |
||
| 412 | ax.set_aspect('equal') |
||
| 413 | |||
| 414 | fig.suptitle('S to select, R to rename, Q to deactivate selector') |
||
| 415 | |||
| 416 | print('S to select, R to rename, Q to deactivate selector') |
||
| 417 | print('(you can select points from one component at a time)') |
||
| 418 | print('(if you use the zoom or movement tools, remember to unselect them)') |
||
| 419 | |||
| 420 | plt.show() |
||
| 421 | |||
| 422 | return mod |
||
| 423 | |||
| 650 | return mod |
||