|
37 | 37 |
|
38 | 38 | from matplotlib.offsetbox import HPacker, VPacker, TextArea, DrawingArea, DraggableOffsetBox |
39 | 39 |
|
| 40 | +import legend_handler |
| 41 | + |
40 | 42 |
|
41 | 43 | class DraggableLegend(DraggableOffsetBox): |
42 | 44 | def __init__(self, legend, use_blit=False, update="loc"): |
@@ -167,6 +169,7 @@ def __init__(self, parent, handles, labels, |
167 | 169 | bbox_to_anchor = None, # bbox that the legend will be anchored. |
168 | 170 | bbox_transform = None, # transform for the bbox |
169 | 171 | frameon = True, # draw frame |
| 172 | + handler_map = None, |
170 | 173 | ): |
171 | 174 | """ |
172 | 175 | - *parent* : the artist that contains the legend |
@@ -236,6 +239,9 @@ def __init__(self, parent, handles, labels, |
236 | 239 | self.legendHandles = [] |
237 | 240 | self._legend_title_box = None |
238 | 241 |
|
| 242 | + |
| 243 | + self._handler_map = handler_map |
| 244 | + |
239 | 245 | localdict = locals() |
240 | 246 |
|
241 | 247 | for name in propnames: |
@@ -470,6 +476,36 @@ def _approx_text_height(self, renderer=None): |
470 | 476 | return renderer.points_to_pixels(self._fontsize) |
471 | 477 |
|
472 | 478 |
|
| 479 | + _default_handler_map = {Line2D:legend_handler.HandlerLine2D(), |
| 480 | + Patch:legend_handler.HandlerPatch(), |
| 481 | + LineCollection:legend_handler.HandlerLineCollection(), |
| 482 | + RegularPolyCollection:legend_handler.HandlerRegularPolyCollection(), |
| 483 | + CircleCollection:legend_handler.HandlerCircleCollection() |
| 484 | + } |
| 485 | + |
| 486 | + def get_legend_handler_map(self): |
| 487 | + if self._handler_map: |
| 488 | + hm = self._default_handler_map.copy() |
| 489 | + hm.update(self._handler_map) |
| 490 | + return hm |
| 491 | + else: |
| 492 | + return self._default_handler_map |
| 493 | + |
| 494 | + def get_legend_handler(self, legend_handler_map, orig_handle): |
| 495 | + if orig_handle in legend_handler_map: |
| 496 | + handler = legend_handler_map[orig_handle] |
| 497 | + else: |
| 498 | + |
| 499 | + for handle_type in type(orig_handle).mro(): |
| 500 | + if handle_type in legend_handler_map: |
| 501 | + handler = legend_handler_map[handle_type] |
| 502 | + break |
| 503 | + else: |
| 504 | + handler = None |
| 505 | + |
| 506 | + return handler |
| 507 | + |
| 508 | + |
473 | 509 | def _init_legend_box(self, handles, labels): |
474 | 510 | """ |
475 | 511 | Initiallize the legend_box. The legend_box is an instance of |
@@ -516,154 +552,37 @@ def _init_legend_box(self, handles, labels): |
516 | 552 | # manually set their transform to the self.get_transform(). |
517 | 553 |
|
518 | 554 |
|
519 | | - for handle, lab in zip(handles, labels): |
520 | | - if isinstance(handle, RegularPolyCollection) or \ |
521 | | - isinstance(handle, CircleCollection): |
522 | | - npoints = self.scatterpoints |
523 | | - else: |
524 | | - npoints = self.numpoints |
525 | | - if npoints > 1: |
526 | | - # we put some pad here to compensate the size of the |
527 | | - # marker |
528 | | - xdata = np.linspace(0.3*fontsize, |
529 | | - (self.handlelength-0.3)*fontsize, |
530 | | - npoints) |
531 | | - xdata_marker = xdata |
532 | | - elif npoints == 1: |
533 | | - xdata = np.linspace(0, self.handlelength*fontsize, 2) |
534 | | - xdata_marker = [0.5*self.handlelength*fontsize] |
535 | | - |
536 | | - if isinstance(handle, Line2D): |
537 | | - ydata = ((height-descent)/2.)*np.ones(xdata.shape, float) |
538 | | - legline = Line2D(xdata, ydata) |
539 | | - |
540 | | - legline.update_from(handle) |
541 | | - self._set_artist_props(legline) # after update |
542 | | - legline.set_clip_box(None) |
543 | | - legline.set_clip_path(None) |
544 | | - legline.set_drawstyle('default') |
545 | | - legline.set_marker('None') |
546 | | - |
547 | | - handle_list.append(legline) |
548 | | - |
549 | | - legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)]) |
550 | | - legline_marker.update_from(handle) |
551 | | - self._set_artist_props(legline_marker) |
552 | | - legline_marker.set_clip_box(None) |
553 | | - legline_marker.set_clip_path(None) |
554 | | - legline_marker.set_linestyle('None') |
555 | | - if self.markerscale !=1: |
556 | | - newsz = legline_marker.get_markersize()*self.markerscale |
557 | | - legline_marker.set_markersize(newsz) |
558 | | - # we don't want to add this to the return list because |
559 | | - # the texts and handles are assumed to be in one-to-one |
560 | | - # correpondence. |
561 | | - legline._legmarker = legline_marker |
562 | | - |
563 | | - elif isinstance(handle, Patch): |
564 | | - p = Rectangle(xy=(0., 0.), |
565 | | - width = self.handlelength*fontsize, |
566 | | - height=(height-descent), |
567 | | - ) |
568 | | - p.update_from(handle) |
569 | | - self._set_artist_props(p) |
570 | | - p.set_clip_box(None) |
571 | | - p.set_clip_path(None) |
572 | | - handle_list.append(p) |
573 | | - elif isinstance(handle, LineCollection): |
574 | | - ydata = ((height-descent)/2.)*np.ones(xdata.shape, float) |
575 | | - legline = Line2D(xdata, ydata) |
576 | | - self._set_artist_props(legline) |
577 | | - legline.set_clip_box(None) |
578 | | - legline.set_clip_path(None) |
579 | | - lw = handle.get_linewidth()[0] |
580 | | - dashes = handle.get_dashes()[0] |
581 | | - color = handle.get_colors()[0] |
582 | | - legline.set_color(color) |
583 | | - legline.set_linewidth(lw) |
584 | | - if dashes[0] is not None: # dashed line |
585 | | - legline.set_dashes(dashes[1]) |
586 | | - handle_list.append(legline) |
587 | | - |
588 | | - elif isinstance(handle, RegularPolyCollection): |
589 | | - |
590 | | - #ydata = self._scatteryoffsets |
591 | | - ydata = height*self._scatteryoffsets |
592 | | - |
593 | | - size_max, size_min = max(handle.get_sizes())*self.markerscale**2,\ |
594 | | - min(handle.get_sizes())*self.markerscale**2 |
595 | | - if self.scatterpoints < 4: |
596 | | - sizes = [.5*(size_max+size_min), size_max, |
597 | | - size_min] |
598 | | - else: |
599 | | - sizes = (size_max-size_min)*np.linspace(0,1,self.scatterpoints)+size_min |
600 | | - |
601 | | - p = type(handle)(handle.get_numsides(), |
602 | | - rotation=handle.get_rotation(), |
603 | | - sizes=sizes, |
604 | | - offsets=zip(xdata_marker,ydata), |
605 | | - transOffset=self.get_transform(), |
606 | | - ) |
607 | | - |
608 | | - p.update_from(handle) |
609 | | - p.set_figure(self.figure) |
610 | | - p.set_clip_box(None) |
611 | | - p.set_clip_path(None) |
612 | | - handle_list.append(p) |
613 | | - |
614 | | - elif isinstance(handle, CircleCollection): |
615 | | - |
616 | | - ydata = height*self._scatteryoffsets |
617 | | - |
618 | | - size_max, size_min = max(handle.get_sizes())*self.markerscale**2,\ |
619 | | - min(handle.get_sizes())*self.markerscale**2 |
620 | | - if self.scatterpoints < 4: |
621 | | - sizes = [.5*(size_max+size_min), size_max, |
622 | | - size_min] |
623 | | - else: |
624 | | - sizes = (size_max-size_min)*np.linspace(0,1,self.scatterpoints)+size_min |
625 | | - |
626 | | - p = type(handle)(sizes, |
627 | | - offsets=zip(xdata_marker,ydata), |
628 | | - transOffset=self.get_transform(), |
629 | | - ) |
630 | | - |
631 | | - p.update_from(handle) |
632 | | - p.set_figure(self.figure) |
633 | | - p.set_clip_box(None) |
634 | | - p.set_clip_path(None) |
635 | | - handle_list.append(p) |
636 | | - else: |
637 | | - handle_type = type(handle) |
638 | | - warnings.warn("Legend does not support %s\nUse proxy artist instead.\n\nhttp://matplotlib.sourceforge.net/users/legend_guide.html#using-proxy-artist\n" % (str(handle_type),)) |
639 | | - handle_list.append(None) |
| 555 | + legend_handler_map = self.get_legend_handler_map() |
640 | 556 |
|
| 557 | + for orig_handle, lab in zip(handles, labels): |
641 | 558 |
|
| 559 | + handler = self.get_legend_handler(legend_handler_map, orig_handle) |
642 | 560 |
|
643 | | - handle = handle_list[-1] |
644 | | - if handle is not None: # handle is None is the artist is not supproted |
645 | | - textbox = TextArea(lab, textprops=label_prop, |
646 | | - multilinebaseline=True, minimumdescent=True) |
647 | | - text_list.append(textbox._text) |
| 561 | + if handler is None: |
| 562 | + warnings.warn("Legend does not support %s\nUse proxy artist instead.\n\nhttp://matplotlib.sourceforge.net/users/legend_guide.html#using-proxy-artist\n" % (str(orig_handle),)) |
| 563 | + handle_list.append(None) |
| 564 | + continue |
| 565 | + |
| 566 | + textbox = TextArea(lab, textprops=label_prop, |
| 567 | + multilinebaseline=True, minimumdescent=True) |
| 568 | + text_list.append(textbox._text) |
648 | 569 |
|
649 | | - labelboxes.append(textbox) |
| 570 | + labelboxes.append(textbox) |
650 | 571 |
|
651 | | - handlebox = DrawingArea(width=self.handlelength*fontsize, |
652 | | - height=height, |
653 | | - xdescent=0., ydescent=descent) |
| 572 | + handlebox = DrawingArea(width=self.handlelength*fontsize, |
| 573 | + height=height, |
| 574 | + xdescent=0., ydescent=descent) |
654 | 575 |
|
655 | | - handlebox.add_artist(handle) |
| 576 | + handle = handler(self, orig_handle, \ |
| 577 | + #xdescent, ydescent, width, height, |
| 578 | + fontsize, |
| 579 | + handlebox) |
| 580 | + handle_list.append(handle) |
| 581 | + |
656 | 582 |
|
657 | | - # special case for collection instances |
658 | | - if isinstance(handle, RegularPolyCollection) or \ |
659 | | - isinstance(handle, CircleCollection): |
660 | | - handle._transOffset = handlebox.get_transform() |
661 | | - handle.set_transform(None) |
662 | 583 |
|
663 | 584 |
|
664 | | - if hasattr(handle, "_legmarker"): |
665 | | - handlebox.add_artist(handle._legmarker) |
666 | | - handleboxes.append(handlebox) |
| 585 | + handleboxes.append(handlebox) |
667 | 586 |
|
668 | 587 |
|
669 | 588 | if len(handleboxes) > 0: |
|
0 commit comments