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