|
2 | 2 | unicode_literals)
|
3 | 3 | import six
|
4 | 4 |
|
5 |
| -from matplotlib import docstring |
| 5 | +from matplotlib import docstring, transforms |
6 | 6 | from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
|
7 | 7 | DrawingArea, TextArea, VPacker)
|
8 |
| -from matplotlib.patches import Rectangle, Ellipse |
9 |
| - |
| 8 | +from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle, |
| 9 | + FancyArrowPatch, PathPatch) |
| 10 | +from matplotlib.text import TextPath |
10 | 11 |
|
11 | 12 | __all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
|
12 |
| - 'AnchoredEllipse', 'AnchoredSizeBar'] |
| 13 | + 'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows'] |
13 | 14 |
|
14 | 15 |
|
15 | 16 | class AnchoredDrawingArea(AnchoredOffsetbox):
|
@@ -374,3 +375,222 @@ def __init__(self, transform, size, label, loc,
|
374 | 375 | child=self._box,
|
375 | 376 | prop=fontproperties,
|
376 | 377 | frameon=frameon, **kwargs)
|
| 378 | + |
| 379 | + |
| 380 | +class AnchoredDirectionArrows(AnchoredOffsetbox): |
| 381 | + @docstring.dedent |
| 382 | + def __init__(self, transform, label_x, label_y, length=0.15, |
| 383 | + fontsize=0.08, loc=2, angle=0, aspect_ratio=1, pad=0.4, |
| 384 | + borderpad=0.4, frameon=False, color='w', alpha=1, |
| 385 | + sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15, |
| 386 | + head_width=10, head_length=15, tail_width=2, |
| 387 | + text_props=None, arrow_props=None, |
| 388 | + **kwargs): |
| 389 | + """ |
| 390 | + Draw two perpendicular arrows to indicate directions. |
| 391 | +
|
| 392 | + Parameters |
| 393 | + ---------- |
| 394 | + transform : `matplotlib.transforms.Transform` |
| 395 | + The transformation object for the coordinate system in use, i.e., |
| 396 | + :attr:`matplotlib.axes.Axes.transAxes`. |
| 397 | +
|
| 398 | + label_x, label_y : string |
| 399 | + Label text for the x and y arrows |
| 400 | +
|
| 401 | + length : int or float, optional |
| 402 | + Length of the arrow, given in coordinates of |
| 403 | + *transform*. |
| 404 | + Defaults to 0.15. |
| 405 | +
|
| 406 | + fontsize : int, optional |
| 407 | + Size of label strings, given in coordinates of *transform*. |
| 408 | + Defaults to 0.08. |
| 409 | +
|
| 410 | + loc : int, optional |
| 411 | + Location of the direction arrows. Valid location codes are:: |
| 412 | +
|
| 413 | + 'upper right' : 1, |
| 414 | + 'upper left' : 2, |
| 415 | + 'lower left' : 3, |
| 416 | + 'lower right' : 4, |
| 417 | + 'right' : 5, |
| 418 | + 'center left' : 6, |
| 419 | + 'center right' : 7, |
| 420 | + 'lower center' : 8, |
| 421 | + 'upper center' : 9, |
| 422 | + 'center' : 10 |
| 423 | +
|
| 424 | + Defaults to 2. |
| 425 | +
|
| 426 | + angle : int or float, optional |
| 427 | + The angle of the arrows in degrees. |
| 428 | + Defaults to 0. |
| 429 | +
|
| 430 | + aspect_ratio : int or float, optional |
| 431 | + The ratio of the length of arrow_x and arrow_y. |
| 432 | + Negative numbers can be used to change the direction. |
| 433 | + Defaults to 1. |
| 434 | +
|
| 435 | + pad : int or float, optional |
| 436 | + Padding around the labels and arrows, in fraction of the font |
| 437 | + size. Defaults to 0.4. |
| 438 | +
|
| 439 | + borderpad : int or float, optional |
| 440 | + Border padding, in fraction of the font size. |
| 441 | + Defaults to 0.4. |
| 442 | +
|
| 443 | + frameon : bool, optional |
| 444 | + If True, draw a box around the arrows and labels. |
| 445 | + Defaults to False. |
| 446 | +
|
| 447 | + color : str, optional |
| 448 | + Color for the arrows and labels. |
| 449 | + Defaults to white. |
| 450 | +
|
| 451 | + alpha : int or float, optional |
| 452 | + Alpha values of the arrows and labels |
| 453 | + Defaults to 1. |
| 454 | +
|
| 455 | + sep_x, sep_y : int or float, optional |
| 456 | + Separation between the arrows and labels in coordinates of |
| 457 | + *transform*. Defaults to 0.01 and 0. |
| 458 | +
|
| 459 | + fontproperties : `matplotlib.font_manager.FontProperties`, optional |
| 460 | + Font properties for the label text. |
| 461 | +
|
| 462 | + back_length : float, optional |
| 463 | + Fraction of the arrow behind the arrow crossing. |
| 464 | + Defaults to 0.15. |
| 465 | +
|
| 466 | + head_width : int or float, optional |
| 467 | + Width of arrow head, sent to ArrowStyle. |
| 468 | + Defaults to 10. |
| 469 | +
|
| 470 | + head_length : int or float, optional |
| 471 | + Length of arrow head, sent to ArrowStyle. |
| 472 | + Defaults to 15. |
| 473 | +
|
| 474 | + tail_width : int or float, optional |
| 475 | + Width of arrow tail, sent to ArrowStyle. |
| 476 | + Defaults to 2. |
| 477 | +
|
| 478 | + text_props, arrow_props : dict |
| 479 | + Properties of the text and arrows, passed to |
| 480 | + :class:`matplotlib.text.TextPath` and |
| 481 | + `matplotlib.patches.FancyArrowPatch` |
| 482 | +
|
| 483 | + **kwargs : |
| 484 | + Keyworded arguments to pass to |
| 485 | + :class:`matplotlib.offsetbox.AnchoredOffsetbox`. |
| 486 | +
|
| 487 | + Attributes |
| 488 | + ---------- |
| 489 | + arrow_x, arrow_y : `matplotlib.patches.FancyArrowPatch` |
| 490 | + Arrow x and y |
| 491 | +
|
| 492 | + text_path_x, text_path_y : `matplotlib.text.TextPath` |
| 493 | + Path for arrow labels |
| 494 | +
|
| 495 | + p_x, p_y : `matplotlib.patches.PathPatch` |
| 496 | + Patch for arrow labels |
| 497 | +
|
| 498 | + box : `matplotlib.offsetbox.AuxTransformBox` |
| 499 | + Container for the arrows and labels. |
| 500 | +
|
| 501 | + Notes |
| 502 | + ----- |
| 503 | + If *prop* is passed as a keyword argument, but *fontproperties* is |
| 504 | + not, then *prop* is be assumed to be the intended *fontproperties*. |
| 505 | + Using both *prop* and *fontproperties* is not supported. |
| 506 | +
|
| 507 | + Examples |
| 508 | + -------- |
| 509 | + >>> import matplotlib.pyplot as plt |
| 510 | + >>> import numpy as np |
| 511 | + >>> from mpl_toolkits.axes_grid1.anchored_artists import \ |
| 512 | + ... AnchoredDirectionArrows |
| 513 | + >>> fig, ax = plt.subplots() |
| 514 | + >>> ax.imshow(np.random.random((10,10))) |
| 515 | + >>> arrows = AnchoredDirectionArrows(ax.transAxes, '111', '110') |
| 516 | + >>> ax.add_artist(arrows) |
| 517 | + >>> fig.show() |
| 518 | +
|
| 519 | + Using several of the optional parameters, creating downward pointing |
| 520 | + arrow and high contrast text labels. |
| 521 | +
|
| 522 | + >>> import matplotlib.font_manager as fm |
| 523 | + >>> fontprops = fm.FontProperties(family='monospace') |
| 524 | + >>> arrows = AnchoredDirectionArrows(ax.transAxes, 'East', 'South', |
| 525 | + ... loc='lower left', color='k', aspect_ratio=-1, sep_x=0.02, |
| 526 | + ... sep_y=-0.01, text_props={'ec':'w', 'fc':'k'}, |
| 527 | + ... fontproperties=fontprops) |
| 528 | + """ |
| 529 | + if arrow_props is None: |
| 530 | + arrow_props = {} |
| 531 | + |
| 532 | + if text_props is None: |
| 533 | + text_props = {} |
| 534 | + |
| 535 | + arrowstyle = ArrowStyle("Simple", |
| 536 | + head_width=head_width, |
| 537 | + head_length=head_length, |
| 538 | + tail_width=tail_width) |
| 539 | + |
| 540 | + if fontproperties is None and 'prop' in kwargs: |
| 541 | + fontproperties = kwargs.pop('prop') |
| 542 | + |
| 543 | + if 'color' not in arrow_props: |
| 544 | + arrow_props['color'] = color |
| 545 | + |
| 546 | + if 'alpha' not in arrow_props: |
| 547 | + arrow_props['alpha'] = alpha |
| 548 | + |
| 549 | + if 'color' not in text_props: |
| 550 | + text_props['color'] = color |
| 551 | + |
| 552 | + if 'alpha' not in text_props: |
| 553 | + text_props['alpha'] = alpha |
| 554 | + |
| 555 | + t_start = transform |
| 556 | + t_end = t_start + transforms.Affine2D().rotate_deg(angle) |
| 5
F438
57 | + |
| 558 | + self.box = AuxTransformBox(t_end) |
| 559 | + |
| 560 | + length_x = length |
| 561 | + length_y = length*aspect_ratio |
| 562 | + |
| 563 | + self.arrow_x = FancyArrowPatch( |
| 564 | + (0, back_length*length_y), |
| 565 | + (length_x, back_length*length_y), |
| 566 | + arrowstyle=arrowstyle, |
| 567 | + shrinkA=0.0, |
| 568 | + shrinkB=0.0, |
| 569 | + **arrow_props) |
| 570 | + |
| 571 | + self.arrow_y = FancyArrowPatch( |
| 572 | + (back_length*length_x, 0), |
| 573 | + (back_length*length_x, length_y), |
| 574 | + arrowstyle=arrowstyle, |
| 575 | + shrinkA=0.0, |
| 576 | + shrinkB=0.0, |
| 577 | + **arrow_props) |
| 578 | + |
| 579 | + self.box.add_artist(self.arrow_x) |
| 580 | + self.box.add_artist(self.arrow_y) |
| 581 | + |
| 582 | + text_path_x = TextPath(( |
| 583 | + length_x+sep_x, back_length*length_y+sep_y), label_x, |
| 584 | + size=fontsize, prop=fontproperties) |
| 585 | + self.p_x = PathPatch(text_path_x, transform=t_start, **text_props) |
| 586 | + self.box.add_artist(self.p_x) |
| 587 | + |
| 588 | + text_path_y = TextPath(( |
| 589 | + length_x*back_length+sep_x, length_y*(1-back_length)+sep_y), |
| 590 | + label_y, size=fontsize, prop=fontproperties) |
| 591 | + self.p_y = PathPatch(text_path_y, **text_props) |
| 592 | + self.box.add_artist(self.p_y) |
| 593 | + |
| 594 | + AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad, |
| 595 | + child=self.box, |
| 596 | + frameon=frameon, **kwargs) |
0 commit comments