|
| 1 | +""" |
| 2 | +Defines classes for path effects. The path effects are supported in |
| 3 | +:class:`~matplotlib.text.Text` and :class:`~matplotlib.patches.Patch` |
| 4 | +matplotlib.text.Text. |
| 5 | +""" |
| 6 | + |
| 7 | +from matplotlib.backend_bases import RendererBase |
| 8 | +import matplotlib.transforms as transforms |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +class _Base(object): |
| 13 | + """ |
| 14 | + A base class for PathEffect. Derived must override draw_path method. |
| 15 | + """ |
| 16 | + |
| 17 | + def __init__(self): |
| 18 | + """ |
| 19 | + initializtion. |
| 20 | + """ |
| 21 | + super(_Base, self).__init__() |
| 22 | + |
| 23 | + |
| 24 | + def _update_gc(self, gc, new_gc_dict): |
| 25 | + new_gc_dict = new_gc_dict.copy() |
| 26 | + |
| 27 | + dashes = new_gc_dict.pop("dashes", None) |
| 28 | + if dashes: |
| 29 | + gc.set_dashes(**dashes) |
| 30 | + |
| 31 | + for k, v in new_gc_dict.iteritems(): |
| 32 | + set_method = getattr(gc, 'set_'+k, None) |
| 33 | + if set_method is None or not callable(set_method): |
| 34 | + raise AttributeError('Unknown property %s'%k) |
| 35 | + set_method(v) |
| 36 | + |
| 37 | + return gc |
| 38 | + |
| 39 | + |
| 40 | + def draw_path(self, renderer, gc, tpath, affine, rgbFace): |
| 41 | + """ |
| 42 | + Derived should override this method. The argument is same |
| 43 | + as *draw_path* method of :class:`matplotlib.backend_bases.RendererBase` |
| 44 | + except the first argument is a renderer. The base definition is :: |
| 45 | +
|
| 46 | + def draw_path(self, renderer, gc, tpath, affine, rgbFace): |
| 47 | + renderer.draw_path(gc, tpath, affine, rgbFace) |
| 48 | +
|
| 49 | + """ |
| 50 | + renderer.draw_path(gc, tpath, affine, rgbFace) |
| 51 | + |
| 52 | + def draw_tex(self, renderer, gc, x, y, s, prop, angle, ismath='TeX!'): |
| 53 | + self._draw_text_as_path(renderer, gc, x, y, s, prop, angle, ismath="TeX") |
| 54 | + |
| 55 | + def draw_text(self, renderer, gc, x, y, s, prop, angle, ismath=False): |
| 56 | + self._draw_text_as_path(renderer, gc, x, y, s, prop, angle, ismath) |
| 57 | + |
| 58 | + def _draw_text_as_path(self, renderer, gc, x, y, s, prop, angle, ismath): |
| 59 | + |
| 60 | + path, transform = RendererBase._get_text_path_transform(renderer, |
| 61 | + x, y, s, |
| 62 | + prop, angle, |
| 63 | + ismath) |
| 64 | + color = gc.get_rgb()[:3] |
| 65 | + |
| 66 | + gc.set_linewidth(0.0) |
| 67 | + self.draw_path(renderer, gc, path, transform, rgbFace=color) |
| 68 | + |
| 69 | + |
| 70 | +# def draw_path_collection(self, renderer, |
| 71 | +# gc, master_transform, paths, all_transforms, |
| 72 | +# offsets, offsetTrans, facecolors, edgecolors, |
| 73 | +# linewidths, linestyles, antialiaseds, urls): |
| 74 | +# path_ids = [] |
| 75 | +# for path, transform in renderer._iter_collection_raw_paths( |
| 76 | +# master_transform, paths, all_transforms): |
| 77 | +# path_ids.append((path, transform)) |
| 78 | + |
| 79 | +# for xo, yo, path_id, gc0, rgbFace in renderer._iter_collection( |
| 80 | +# gc, path_ids, offsets, offsetTrans, facecolors, edgecolors, |
| 81 | +# linewidths, linestyles, antialiaseds, urls): |
| 82 | +# path, transform = path_id |
| 83 | +# transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo) |
| 84 | +# self.draw_path(renderer, gc0, path, transform, rgbFace) |
| 85 | + |
| 86 | + |
| 87 | +class Normal(_Base): |
| 88 | + """ |
| 89 | + path effect with no effect |
| 90 | + """ |
| 91 | + pass |
| 92 | + |
| 93 | +class Stroke(_Base): |
| 94 | + """ |
| 95 | + stroke the path with updated gc. |
| 96 | + """ |
| 97 | + |
| 98 | + def __init__(self, **kwargs): |
| 99 | + """ |
| 100 | + The path will be stroked with its gc updated with the given |
| 101 | + keyword arguments, i.e., the keyword arguments should be valid |
| 102 | + gc parameter values. |
| 103 | + """ |
| 104 | + super(Stroke, self).__init__() |
| 105 | + self._gc = kwargs |
| 106 | + |
| 107 | + def draw_path(self, renderer, gc, tpath, affine, rgbFace): |
| 108 | + """ |
| 109 | + draw the path with update gc. |
| 110 | + """ |
| 111 | + # Do not modify the input! Use copy instead. |
| 112 | + |
| 113 | + gc0 = renderer.new_gc() |
| 114 | + gc0.copy_properties(gc) |
| 115 | + |
| 116 | + gc0 = self._update_gc(gc0, self._gc) |
| 117 | + renderer.draw_path(gc0, tpath, affine, None) |
| 118 | + |
| 119 | + |
| 120 | +class withStroke(Stroke): |
| 121 | + |
| 122 | + """ |
| 123 | + Same as Stroke, but add a stroke with the original gc at the end. |
| 124 | + """ |
| 125 | + |
| 126 | + def draw_path(self, renderer, gc, tpath, affine, rgbFace): |
| 127 | + |
| 128 | + Stroke.draw_path(self, renderer, gc, tpath, affine, rgbFace) |
| 129 | + renderer.draw_path(gc, tpath, affine, rgbFace) |
| 130 | + |
| 131 | + |
| 132 | +import matplotlib.transforms as mtransforms |
| 133 | + |
| 134 | +class SimplePatchShadow(_Base): |
| 135 | + """ |
| 136 | + simple shadow |
| 137 | + """ |
| 138 | + |
| 139 | + def __init__(self, offset_xy=(2,-2), |
| 140 | + shadow_rgbFace=None, patch_alpha=0.7, |
| 141 | + **kwargs): |
| 142 | + """ |
| 143 | + """ |
| 144 | + super(_Base, self).__init__() |
| 145 | + self._offset_xy = offset_xy |
| 146 | + self._shadow_rgbFace = shadow_rgbFace |
| 147 | + self._patch_alpha = patch_alpha |
| 148 | + |
| 149 | + self._gc = kwargs |
| 150 | + self._offset_tran = mtransforms.Affine2D() |
| 151 | + |
| 152 | + def draw_path(self, renderer, gc, tpath, affine, rgbFace): |
| 153 | + """ |
| 154 | + """ |
| 155 | + # Do not modify the input! Use copy instead. |
| 156 | + |
| 157 | + offset_x = renderer.points_to_pixels(self._offset_xy[0]) |
| 158 | + offset_y = renderer.points_to_pixels(self._offset_xy[1]) |
| 159 | + |
| 160 | + affine0 = affine + self._offset_tran.clear().translate(offset_x, offset_y) |
| 161 | + |
| 162 | + gc0 = renderer.new_gc() |
| 163 | + gc0.copy_properties(gc) |
| 164 | + |
| 165 | + if self._shadow_rgbFace is None: |
| 166 | + r,g,b = rgbFace |
| 167 | + rho = 0.3 |
| 168 | + r = rho*r |
| 169 | + g = rho*g |
| 170 | + b = rho*b |
| 171 | + |
| 172 | + shadow_rgbFace = (r,g,b) |
| 173 | + else: |
| 174 | + shadow_rgbFace = self._shadow_rgbFace |
| 175 | + |
| 176 | + gc0.set_foreground("none") |
| 177 | + gc0.set_alpha(1.-self._patch_alpha) |
| 178 | + gc0.set_linewidth(0) |
| 179 | + |
| 180 | + gc0 = self._update_gc(gc0, self._gc) |
| 181 | + renderer.draw_path(gc0, tpath, affine0, shadow_rgbFace) |
| 182 | + |
| 183 | + |
| 184 | +class withSimplePatchShadow(SimplePatchShadow): |
| 185 | + """ |
| 186 | + simple shadow |
| 187 | + """ |
| 188 | + |
| 189 | + def draw_path(self, renderer, gc, tpath, affine, rgbFace): |
| 190 | + |
| 191 | + SimplePatchShadow.draw_path(self, renderer, gc, tpath, affine, rgbFace) |
| 192 | + |
| 193 | + gc1 = renderer.new_gc() |
| 194 | + gc1.copy_properties(gc) |
| 195 | + gc1.set_alpha(gc1.get_alpha()*self._patch_alpha) |
| 196 | + renderer.draw_path(gc1, tpath, affine, rgbFace) |
| 197 | + |
| 198 | + |
| 199 | +if __name__ == '__main__': |
| 200 | + clf() |
| 201 | + imshow([[1,2],[2,3]]) |
| 202 | + #eff = PathEffects.Thicken() |
| 203 | + txt = annotate("test", (1., 1.), (0., 0), |
| 204 | + arrowprops=dict(arrowstyle="->", connectionstyle="angle3", lw=2), |
| 205 | + size=12, ha="center") |
| 206 | + txt.set_path_effects([withStroke(linewidth=3, foreground="w")]) |
| 207 | + #txt.arrow_patch.set_path_effects([PathEffects.withStroke(width=3, color="w")]) |
| 208 | + txt.arrow_patch.set_path_effects([Stroke(linewidth=5, foreground="w"), |
| 209 | + Normal()]) |
0 commit comments