def getTransform(parentMap, node): # test, speed up by caching transform for node n = node transform = Affine2D() while True: tString = n.get("transform") if tString is not None: for m in reversed(list(finditer(r"(?P<method>\w+)\(\s*(?P<args>[^)]*)\)", tString))): args = m.group('args').replace(",", " ").split() method = m.group('method') if method == "matrix": transform = composite_transform_factory(transform, Affine2D.from_values(*args)) elif method == "translate": transform.translate(args[0], 0 if len(args)<2 else args[1]) elif method == "scale": transform.scale(*args) elif method == "rotate": if len(args) == 1: transform.rotate_deg(args[0]) else: transform.rotate_deg_around(*args) elif method == "skewX": transform.skew_deg(args[0], 0) elif method == "skewY": transform.skew_deg(0, args[0]) if n in parentMap: n = parentMap[n] else: break return transform
def fit_affine_clip(xy1f, xy2f): sol = fit_affine(xy1f, xy2f) affine_tr = Affine2D.from_values(*sol) xy1f_tr = affine_tr.transform(xy1f) #[:,0], xy1f[:,1]) # mask and refit dx_ = xy1f_tr[:,0] - xy2f[:,0] mystd = dx_.std() mm = [np.abs(dx_) < 3. * mystd] sol = fit_affine(xy1f[mm], xy2f[mm]) affine_tr = Affine2D.from_values(*sol) return affine_tr, mm
def triangular_axes(fig): # ternary projection tr = Affine2D.from_values(1., 0, 0.5, np.sqrt(3)/2., 0, 0) # negative ternary projection for dependent axis neg_tr = Affine2D.from_values(1., 0, -0.5, np.sqrt(3)/2., 0, 0) # identity transform identity_tr = Affine2D.from_values(1, 0, 0, 1, 0, 0) grid_helper = GridHelperTriangular(tr, extremes=(0,1,0,1), grid_type="independent") # use null_locator to kill extra horizontal gridlines from dependent axis null_locator = grid_finder.MaxNLocator(1) dep_grid_helper = GridHelperTriangular(neg_tr, extremes=(0,1,0,1), grid_type="dependent", grid_locator2=null_locator) # Add independent axes with gridlines ax1 = floating_axes.FloatingSubplot(fig, 111, grid_helper=grid_helper) fig.add_subplot(ax1) ax1.axis[:].set_visible(False) ax1.axis["bottom"].set_visible(True) ax1.axis["left"].set_visible(True) # Add dependent axis with gridlines ax2 = ParasiteAxesAuxTrans(ax1, identity_tr, "equal", grid_helper=dep_grid_helper) ax2.axis["right"] = ax2.get_grid_helper().new_floating_axis(0, 1, axes=ax1) ax2.axis["right"].toggle(ticklabels=False) ax1.parasites.append(ax2) ax1.grid(True) ax2.grid(True) ax1.plot([]) ax1.set_aspect(1.) return ax1
def test_Affine2D_from_values(): points = [ [0,0], [10,20], [-1,0], ] t = Affine2D.from_values(1,0,0,0,0,0) actual = t.transform(points) expected = np.array( [[0,0],[10,0],[-1,0]] ) assert_almost_equal(actual,expected) t = Affine2D.from_values(0,2,0,0,0,0) actual = t.transform(points) expected = np.array( [[0,0],[0,20],[0,-2]] ) assert_almost_equal(actual,expected) t = Affine2D.from_values(0,0,3,0,0,0) actual = t.transform(points) expected = np.array( [[0,0],[60,0],[0,0]] ) assert_almost_equal(actual,expected) t = Affine2D.from_values(0,0,0,4,0,0) actual = t.transform(points) expected = np.array( [[0,0],[0,80],[0,0]] ) assert_almost_equal(actual,expected) t = Affine2D.from_values(0,0,0,0,5,0) actual = t.transform(points) expected = np.array( [[5,0],[5,0],[5,0]] ) assert_almost_equal(actual,expected) t = Affine2D.from_values(0,0,0,0,0,6) actual = t.transform(points) expected = np.array( [[0,6],[0,6],[0,6]] ) assert_almost_equal(actual,expected)
def device_fill_points(self, points, mode): if mode in (FILL, FILL_STROKE, EOF_FILL_STROKE, EOF_FILL): fill = tuple(self.state.fill_color) else: fill = None if mode in (STROKE, FILL_STROKE, EOF_FILL_STROKE): color = tuple(self.state.line_color) else: color = tuple(self.state.fill_color) path = Path(points) gc = self._backend.new_gc() gc.set_linewidth(self.state.line_width) if not (self.state.line_dash[1] == 0).all(): gc.set_dashes(self.state.line_dash[0], list(self.state.line_dash[1])) if self.state.clipping_path: gc.set_clip_path(self._get_transformed_clip_path()) gc.set_joinstyle(line_join_map[self.state.line_join]) gc.set_capstyle(line_cap_map[self.state.line_cap]) gc.set_foreground(color, isRGB=True) gc.set_alpha(self.state.alpha) transform = Affine2D.from_values(*affine.affine_params(self.get_ctm())) self._backend.draw_path(gc, path, transform, fill) gc.restore()
def device_draw_image(self, img, rect): """ draw_image(img_gc, rect=(x,y,w,h)) Draws another gc into this one. If 'rect' is not provided, then the image gc is drawn into this one, rooted at (0,0) and at full pixel size. If 'rect' is provided, then the image is resized into the (w,h) given and drawn into this GC at point (x,y). img_gc is either a Numeric array (WxHx3 or WxHx4) or a GC from Kiva's Agg backend (kiva.agg.GraphicsContextArray). Requires the Python Imaging Library (PIL). """ from PIL import Image as PilImage from matplotlib import _image # We turn img into a PIL object, since that is what ReportLab # requires. To do this, we first determine if the input image # GC needs to be converted to RGBA/RGB. If so, we see if we can # do it nicely (using convert_pixel_format), and if not, we do # it brute-force using Agg. if type(img) == type(array([])): # Numeric array converted_img = agg.GraphicsContextArray(img, pix_format='rgba32') format = 'RGBA' elif isinstance(img, agg.GraphicsContextArray): if img.format().startswith('RGBA'): format = 'RGBA' elif img.format().startswith('RGB'): format = 'RGB' else: converted_img = img.convert_pixel_format('rgba32', inplace=0) format = 'RGBA' # Should probably take this into account # interp = img.get_image_interpolation() else: warnings.warn("Cannot render image of type %r into SVG context." % type(img)) return if rect == None: rect = (0, 0, img.width(), img.height()) width, height = img.width(), img.height() # converted_img now holds an Agg graphics context with the image pil_img = PilImage.fromstring(format, (converted_img.width(), converted_img.height()), converted_img.bmp_array.tostring()) left, top, width, height = rect if width != img.width() or height != img.height(): # This is not strictly required. pil_img = pil_img.resize((int(width), int(height)), PilImage.NEAREST) pil_img = pil_img.transpose(PilImage.FLIP_TOP_BOTTOM) # Fix for the SVG backend, which seems to flip x when a transform is provided. if self._backend.flipy(): pil_img = pil_img.transpose(PilImage.FLIP_LEFT_RIGHT) mpl_img = _image.frombuffer(pil_img.tostring(), width, height, True) mpl_img.is_grayscale = False gc = self._backend.new_gc() if self.state.clipping_path: gc.set_clip_path(self._get_transformed_clip_path()) transform = Affine2D.from_values(*affine.affine_params(self.get_ctm())) self._backend.draw_image(gc, left, top, mpl_img, dx=width, dy=height, transform=transform) gc.restore()
def _make_flip_transform(self, transform): return (transform + Affine2D().scale(1.0, -1.0).translate(0.0, self.height))
def _set_lim_and_transforms(self): """ This is called once when the plot is created to set up all the transforms for the data, text and grids. """ # There are three important coordinate spaces going on here: # # 1. Data space: The space of the data itself # # 2. Axes space: The unit rectangle (0, 0) to (1, 1) # covering the entire plot area. # # 3. Display space: The coordinates of the resulting image, # often in pixels or dpi/inch. # This function makes heavy use of the Transform classes in # ``lib/matplotlib/transforms.py.`` For more information, see # the inline documentation there. # The goal of the first two transformations is to get from the # data space (in this case longitude and latitude) to axes # space. It is separated into a non-affine and affine part so # that the non-affine part does not have to be recomputed when # a simple affine change to the figure has been made (such as # resizing the window or changing the dpi). # 1) The core transformation from data space into # rectilinear space defined in the HammerTransform class. self.transProjection = IdentityTransform() # 2) The above has an output range that is not in the unit # rectangle, so scale and translate it so it fits correctly # within the axes. The peculiar calculations of xscale and # yscale are specific to a Aitoff-Hammer projection, so don't # worry about them too much. self.transAffine = Affine2D.from_values( 1., 0, 0.5, np.sqrt(3)/2., 0, 0) self.transAffinedep = Affine2D.from_values( 1., 0, -0.5, np.sqrt(3)/2., 0, 0) #self.transAffine = IdentityTransform() # 3) This is the transformation from axes space to display # space. self.transAxes = BboxTransformTo(self.bbox) # Now put these 3 transforms together -- from data all the way # to display coordinates. Using the '+' operator, these # transforms will be applied "in order". The transforms are # automatically simplified, if possible, by the underlying # transformation framework. self.transData = \ self.transProjection + \ self.transAffine + \ self.transAxes # The main data transformation is set up. Now deal with # gridlines and tick labels. # Longitude gridlines and ticklabels. The input to these # transforms are in display space in x and axes space in y. # Therefore, the input values will be in range (-xmin, 0), # (xmax, 1). The goal of these transforms is to go from that # space to display space. The tick labels will be offset 4 # pixels from the equator. self._xaxis_pretransform = IdentityTransform() self._xaxis_transform = \ self._xaxis_pretransform + \ self.transData self._xaxis_text1_transform = \ Affine2D().scale(1.0, 0.0) + \ self.transData + \ Affine2D().translate(0.0, -20.0) self._xaxis_text2_transform = \ Affine2D().scale(1.0, 0.0) + \ self.transData + \ Affine2D().translate(0.0, -4.0) # Now set up the transforms for the latitude ticks. The input to # these transforms are in axes space in x and display space in # y. Therefore, the input values will be in range (0, -ymin), # (1, ymax). The goal of these transforms is to go from that # space to display space. The tick labels will be offset 4 # pixels from the edge of the axes ellipse. self._yaxis_transform = self.transData yaxis_text_base = \ self.transProjection + \ (self.transAffine + \ self.transAxes) self._yaxis_text1_transform = \ yaxis_text_base + \ Affine2D().translate(-8.0, 0.0) self._yaxis_text2_transform = \ yaxis_text_base + \ Affine2D().translate(8.0, 0.0)
def volume_overlay3(ax, quotes, colorup='k', colordown='r', width=4, alpha=1.0): """Add a volume overlay to the current axes. quotes is a list of (d, open, high, low, close, volume) and close-open is used to determine the color of the bar Parameters ---------- ax : `Axes` an Axes instance to plot to quotes : sequence of (time, open, high, low, close, ...) sequences data to plot. time must be in float date format - see date2num width : int the bar width in points colorup : color the color of the lines where close1 >= close0 colordown : color the color of the lines where close1 < close0 alpha : float bar transparency Returns ------- ret : `barCollection` The `barrCollection` added to the axes """ colorup = mcolors.to_rgba(colorup, alpha) colordown = mcolors.to_rgba(colordown, alpha) colord = {True: colorup, False: colordown} dates, opens, highs, lows, closes, volumes = list(zip(*quotes)) colors = [colord[close1 >= close0] for close0, close1 in zip(closes[:-1], closes[1:]) if close0 != -1 and close1 != -1] colors.insert(0, colord[closes[0] >= opens[0]]) right = width / 2.0 left = -width / 2.0 bars = [((left, 0), (left, volume), (right, volume), (right, 0)) for d, open, high, low, close, volume in quotes] sx = ax.figure.dpi * (1.0 / 72.0) # scale for points sy = ax.bbox.height / ax.viewLim.height barTransform = Affine2D().scale(sx, sy) dates = [d for d, open, high, low, close, volume in quotes] offsetsBars = [(d, 0) for d in dates] useAA = 0, # use tuple here lw = 0.5, # and here barCollection = PolyCollection(bars, facecolors=colors, edgecolors=((0, 0, 0, 1),), antialiaseds=useAA, linewidths=lw, offsets=offsetsBars, transOffset=ax.transData, ) barCollection.set_transform(barTransform) minpy, maxx = (min(dates), max(dates)) miny = 0 maxy = max([volume for d, open, high, low, close, volume in quotes]) corners = (minpy, miny), (maxx, maxy) ax.update_datalim(corners) # print 'datalim', ax.dataLim.bounds # print 'viewlim', ax.viewLim.bounds ax.add_collection(barCollection) ax.autoscale_view() return barCollection
def ros_map(ax: plt.Axes, yaml_path: str, plot_mode: PlotMode, cmap: str = "Greys_r", mask_unknown_value: int = SETTINGS.ros_map_unknown_cell_value, alpha: float = SETTINGS.ros_map_alpha_value) -> None: """ Inserts an image of an 2D ROS map into the plot axis. See: http://wiki.ros.org/map_server#Map_format :param ax: 2D matplotlib axes :param plot_mode: a 2D PlotMode :param yaml_path: yaml file that contains the metadata of the map image :param cmap: color map used to map scalar data to colors :param mask_unknown_value: uint8 value that represents unknown cells. If specified, these cells will be masked out. If set to None or False, nothing will be masked. """ import yaml if isinstance(ax, Axes3D): raise PlotException("ros_map can't be drawn into a 3D axis") if plot_mode in {PlotMode.xz, PlotMode.yz, PlotMode.zx, PlotMode.zy}: # Image lies in xy / yx plane, nothing to see here. return x_idx, y_idx, _ = plot_mode_to_idx(plot_mode) with open(yaml_path) as f: metadata = yaml.safe_load(f) # Load map image, mask unknown cells if desired. image_path = metadata["image"] if not os.path.isabs(image_path): image_path = os.path.join(os.path.dirname(yaml_path), image_path) image = plt.imread(image_path) if mask_unknown_value: mask_unknown_value = np.uint8(mask_unknown_value) image = np.ma.masked_where(image == mask_unknown_value, image) # Squeeze extent to reflect metric coordinates. resolution = metadata["resolution"] n_rows, n_cols = image.shape[x_idx], image.shape[y_idx] extent = [0, n_cols * resolution, 0, n_rows * resolution] if plot_mode == PlotMode.yx: image = np.rot90(image) image = np.fliplr(image) ax_image = ax.imshow(image, origin="upper", cmap=cmap, extent=extent, zorder=1, alpha=alpha) # Transform map frame to plot axis origin. map_to_pixel_origin = Affine2D() map_to_pixel_origin.translate(metadata["origin"][x_idx], metadata["origin"][y_idx]) angle = metadata["origin"][2] if plot_mode == PlotMode.yx: # Rotation axis (z) points downwards. angle *= -1 map_to_pixel_origin.rotate(angle) ax_image.set_transform(map_to_pixel_origin + ax.transData) # Initially flipped axes are lost for mysterious reasons... if SETTINGS.plot_invert_xaxis: ax.invert_xaxis() if SETTINGS.plot_invert_yaxis: ax.invert_yaxis()
import numpy as np %matplotlib widget px = np.random.rand(10) py = np.random.rand(10) offsets = np.ma.column_stack([px, py]) #%% 1 fig, ax = plt.subplots() reduction = 50 c = Path.unit_circle() c = c.transformed(Affine2D().scale(0.5 * reduction)) collection = PathCollection( (c,), offsets=offsets, transOffset = ax.transData, edgecolor='black', facecolor=(0, 0, 0, .0125), linewidth=1 ) collection.set_transform(IdentityTransform()) ax.add_collection( collection ) #%% 2 collection.set_path_effects([withStroke(linewidth=5, foreground='r')])
def create_line_switch_collection(net, size=1, distance_to_bus=3, use_line_geodata=False, **kwargs): """ Creates a matplotlib patch collection of pandapower switches. INPUT: **net** (pandapowerNet) - The pandapower network OPTIONAL: **size** (float, 1) - Size of the switch patches **distance_to_bus** (float, 3) - Distance of the switch patch from the bus patch **use_line_geodata** (bool, False) - If True, line coordinates are used to identify the switch position **kwargs - Key word arguments are passed to the patch function """ lbs_switches = net.switch.index[net.switch.et == "l"] color = kwargs.pop("color", "k") switch_patches = [] for switch in lbs_switches: sb = net.switch.bus.loc[switch] line = net.line.loc[net.switch.element.loc[switch]] fb = line.from_bus tb = line.to_bus line_buses = set([fb, tb]) target_bus = list(line_buses - set([sb]))[0] if sb not in net.bus_geodata.index or target_bus not in net.bus_geodata.index: logger.warning("Bus coordinates for switch %s not found, skipped switch!" % switch) continue # switch bus and target coordinates pos_sb = net.bus_geodata.loc[sb, ["x", "y"]].values pos_ta = np.zeros(2) use_bus_geodata = False if use_line_geodata: if line.name in net.line_geodata.index: line_coords = net.line_geodata.coords.loc[line.name] # check, which end of the line is nearer to the switch bus if len(line_coords) > 2: if abs(line_coords[0][0] - pos_sb[0]) < 0.01 and \ abs(line_coords[0][1] - pos_sb[1]) < 0.01: pos_ta = np.array([line_coords[1][0], line_coords[1][1]]) else: pos_ta = np.array([line_coords[-2][0], line_coords[-2][1]]) else: use_bus_geodata = True else: use_bus_geodata = True if not use_line_geodata or use_bus_geodata: pos_ta = net.bus_geodata.loc[target_bus, ["x", "y"]] # position of switch symbol vec = pos_ta - pos_sb mag = np.linalg.norm(vec) pos_sw = pos_sb + vec / mag * distance_to_bus # rotation of switch symbol angle = np.arctan2(vec[1], vec[0]) rotation = Affine2D().rotate_around(pos_sw[0], pos_sw[1], angle) # color switch by state col = color if net.switch.closed.loc[switch] else "white" # create switch patch (switch size is respected to center the switch on the line) patch = Rectangle((pos_sw[0] - size/2, pos_sw[1] - size/2), size, size, facecolor=col, edgecolor=color) # apply rotation patch.set_transform(rotation) switch_patches.append(patch) switches = PatchCollection(switch_patches, match_original=True, **kwargs) return switches
def create_cg(st, fig=None, subplot=111): """ Helper function to create curvelinear grid The function makes use of the Matplotlib AXISARTIST namespace `mpl_toolkits.axisartist \ <https://matplotlib.org/mpl_toolkits/axes_grid/users/axisartist.html>`_. Here are some limitations to normal Matplotlib Axes. While using the Matplotlib `AxesGrid Toolkit \ <https://matplotlib.org/mpl_toolkits/axes_grid/index.html>`_ most of the limitations can be overcome. See `Matplotlib AxesGrid Toolkit User’s Guide \ <https://matplotlib.org/mpl_toolkits/axes_grid/users/index.html>`_. Parameters ---------- st : string scan type, 'PPI' or 'RHI' fig : matplotlib Figure object If given, the PPI will be plotted into this figure object. Axes are created as needed. If None a new figure object will be created or current figure will be used, depending on "subplot". subplot : :class:`matplotlib:matplotlib.gridspec.GridSpec`, \ matplotlib grid definition nrows/ncols/plotnumber, see examples section defaults to '111', only one subplot Returns ------- cgax : matplotlib toolkit axisartist Axes object curvelinear Axes (r-theta-grid) caax : matplotlib Axes object (twin to cgax) Cartesian Axes (x-y-grid) for plotting cartesian data paax : matplotlib Axes object (parasite to cgax) The parasite axes object for plotting polar data """ if st == 'RHI': # create transformation tr = Affine2D().scale(np.pi / 180, 1) + PolarAxes.PolarTransform() # build up curvelinear grid extreme_finder = ah.ExtremeFinderCycle(20, 20, lon_cycle=100, lat_cycle=None, lon_minmax=(0, np.inf), lat_minmax=(0, np.inf), ) # locator and formatter for angular annotation grid_locator1 = ah.LocatorDMS(10.) tick_formatter1 = ah.FormatterDMS() # grid_helper for curvelinear grid grid_helper = GridHelperCurveLinear(tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, grid_locator2=None, tick_formatter1=tick_formatter1, tick_formatter2=None, ) # try to set nice locations for range gridlines grid_helper.grid_finder.grid_locator2._nbins = 30.0 grid_helper.grid_finder.grid_locator2._steps = [0, 1, 1.5, 2, 2.5, 5, 10] if st == 'PPI': # Set theta start to north tr_rotate = Affine2D().translate(-90, 0) # set theta running clockwise tr_scale = Affine2D().scale(-np.pi / 180, 1) # create transformation tr = tr_rotate + tr_scale + PolarAxes.PolarTransform() # build up curvelinear grid extreme_finder = ah.ExtremeFinderCycle(20, 20, lon_cycle=360, lat_cycle=None, lon_minmax=(360, 0), lat_minmax=(0, np.inf), ) # locator and formatter for angle annotation locs = [i for i in np.arange(0., 359., 10.)] grid_locator1 = FixedLocator(locs) tick_formatter1 = DictFormatter(dict([(i, r"${0:.0f}^\circ$".format(i)) for i in locs])) # grid_helper for curvelinear grid grid_helper = GridHelperCurveLinear(tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, grid_locator2=None, tick_formatter1=tick_formatter1, tick_formatter2=None, ) # try to set nice locations for range gridlines grid_helper.grid_finder.grid_locator2._nbins = 15.0 grid_helper.grid_finder.grid_locator2._steps = [0, 1, 1.5, 2, 2.5, 5, 10] # if there is no figure object given if fig is None: # create new figure if there is only one subplot if subplot is 111: fig = pl.figure() # otherwise get current figure or create new figure else: fig = pl.gcf() # generate Axis cgax = SubplotHost(fig, subplot, grid_helper=grid_helper) fig.add_axes(cgax) # PPIs always plottetd with equal aspect if st == 'PPI': cgax.set_aspect('equal', adjustable='box') # get twin axis for cartesian grid caax = cgax.twin() # move axis annotation from right to left and top to bottom for # cartesian axis caax.toggle_axisline() # make right and top axis visible and show ticklabels (curvelinear axis) cgax.axis["top", "right"].set_visible(True) cgax.axis["top", "right"].major_ticklabels.set_visible(True) # make ticklabels of left and bottom axis invisible (curvelinear axis) cgax.axis["left", "bottom"].major_ticklabels.set_visible(False) # and also set tickmarklength to zero for better presentation # (curvelinear axis) cgax.axis["top", "right", "left", "bottom"].major_ticks.set_ticksize(0) # show theta (angles) on top and right axis cgax.axis["top"].get_helper().nth_coord_ticks = 0 cgax.axis["right"].get_helper().nth_coord_ticks = 0 # generate and add parasite axes with given transform paax = ParasiteAxesAuxTrans(cgax, tr, "equal") # note that paax.transData == tr + cgax.transData # Anything you draw in paax will match the ticks and grids of cgax. cgax.parasites.append(paax) return cgax, caax, paax
def curvelinear_test3(fig): """ polar projection, but in a rectangular box. """ global ax1, axis import numpy as np from . import angle_helper from matplotlib.projections import PolarAxes from matplotlib.transforms import Affine2D from mpl_toolkits.axes_grid.parasite_axes import SubplotHost # PolarAxes.PolarTransform takes radian. However, we want our coordinate # system in degree tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() # polar projection, which involves cycle, and also has limits in # its coordinates, needs a special method to find the extremes # (min, max of the coordinate within the view). # 20, 20 : number of sampling points along x, y direction extreme_finder = angle_helper.ExtremeFinderCycle( 20, 20, lon_cycle=360, lat_cycle=None, lon_minmax=None, lat_minmax=(0, np.inf), ) grid_locator1 = angle_helper.LocatorDMS(12) # Find a grid values appropriate for the coordinate (degree, # minute, second). tick_formatter1 = angle_helper.FormatterDMS() # And also uses an appropriate formatter. Note that,the # acceptable Locator and Formatter class is a bit different than # that of mpl's, and you cannot directly use mpl's Locator and # Formatter here (but may be possible in the future). grid_helper = GridHelperCurveLinear(tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, tick_formatter1=tick_formatter1) ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) for axis in list(six.itervalues(ax1.axis)): axis.set_visible(False) fig.add_subplot(ax1) grid_helper = ax1.get_grid_helper() ax1.axis["lat1"] = axis = grid_helper.new_floating_axis( 0, 130, axes=ax1, axis_direction="left") axis.label.set_text("Test") axis.label.set_visible(True) axis.get_helper()._extremes = 0.001, 10 grid_helper = ax1.get_grid_helper() ax1.axis["lat2"] = axis = grid_helper.new_floating_axis( 0, 50, axes=ax1, axis_direction="right") axis.label.set_text("Test") axis.label.set_visible(True) axis.get_helper()._extremes = 0.001, 10 ax1.axis["lon"] = axis = grid_helper.new_floating_axis( 1, 10, axes=ax1, axis_direction="bottom") axis.label.set_text("Test 2") axis.get_helper()._extremes = 50, 130 axis.major_ticklabels.set_axis_direction("top") axis.label.set_axis_direction("top") grid_helper.grid_finder.grid_locator1.den = 5 grid_helper.grid_finder.grid_locator2._nbins = 5 # # A parasite axes with given transform # ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal") # # note that ax2.transData == tr + ax1.transData # # Anthing you draw in ax2 will match the ticks and grids of ax1. # ax1.parasites.append(ax2) # intp = cbook.simple_linear_interpolation # ax2.plot(intp(np.array([0, 30]), 50), # intp(np.array([10., 10.]), 50)) ax1.set_aspect(1.) ax1.set_xlim(-5, 12) ax1.set_ylim(-5, 10) ax1.grid(True)
def plot_day_summary2( ax, opens, closes, highs, lows, ticksize=4, colorup='k', colordown='r', ): """ Represent the time, open, close, high, low as a vertical line ranging from low to high. The left tick is the open and the right tick is the close. ax : an Axes instance to plot to ticksize : size of open and close ticks in points colorup : the color of the lines where close >= open colordown : the color of the lines where close < open return value is a list of lines added """ # note this code assumes if any value open, close, low, high is # missing they all are missing rangeSegments = [((i, low), (i, high)) for i, low, high in zip(xrange(len(lows)), lows, highs) if low != -1] # the ticks will be from ticksize to 0 in points at the origin and # we'll translate these to the i, close location openSegments = [((-ticksize, 0), (0, 0))] # the ticks will be from 0 to ticksize in points at the origin and # we'll translate these to the i, close location closeSegments = [((0, 0), (ticksize, 0))] offsetsOpen = [(i, open) for i, open in zip(xrange(len(opens)), opens) if open != -1] offsetsClose = [(i, close) for i, close in zip(xrange(len(closes)), closes) if close != -1] scale = ax.figure.dpi * (1.0 / 72.0) tickTransform = Affine2D().scale(scale, 0.0) r, g, b = colorConverter.to_rgb(colorup) colorup = r, g, b, 1 r, g, b = colorConverter.to_rgb(colordown) colordown = r, g, b, 1 colord = { True: colorup, False: colordown, } colors = [ colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1 ] assert (len(rangeSegments) == len(offsetsOpen)) assert (len(offsetsOpen) == len(offsetsClose)) assert (len(offsetsClose) == len(colors)) useAA = 0, # use tuple here lw = 1, # and here rangeCollection = LineCollection( rangeSegments, colors=colors, linewidths=lw, antialiaseds=useAA, ) openCollection = LineCollection( openSegments, colors=colors, antialiaseds=useAA, linewidths=lw, offsets=offsetsOpen, transOffset=ax.transData, ) openCollection.set_transform(tickTransform) closeCollection = LineCollection( closeSegments, colors=colors, antialiaseds=useAA, linewidths=lw, offsets=offsetsClose, transOffset=ax.transData, ) closeCollection.set_transform(tickTransform) minpy, maxx = (0, len(rangeSegments)) miny = min([low for low in lows if low != -1]) maxy = max([high for high in highs if high != -1]) corners = (minpy, miny), (maxx, maxy) ax.update_datalim(corners) ax.autoscale_view() # add these last ax.add_collection(rangeCollection) ax.add_collection(openCollection) ax.add_collection(closeCollection) return rangeCollection, openCollection, closeCollection
def volume_overlay3(ax, quotes, colorup='k', colordown='r', width=4, alpha=1.0): """ Add a volume overlay to the current axes. quotes is a list of (d, open, close, high, low, volume) and close-open is used to determine the color of the bar kwarg width : the bar width in points colorup : the color of the lines where close1 >= close0 colordown : the color of the lines where close1 < close0 alpha : bar transparency """ r, g, b = colorConverter.to_rgb(colorup) colorup = r, g, b, alpha r, g, b = colorConverter.to_rgb(colordown) colordown = r, g, b, alpha colord = { True: colorup, False: colordown, } dates, opens, closes, highs, lows, volumes = zip(*quotes) colors = [ colord[close1 >= close0] for close0, close1 in zip(closes[:-1], closes[1:]) if close0 != -1 and close1 != -1 ] colors.insert(0, colord[closes[0] >= opens[0]]) right = width / 2.0 left = -width / 2.0 bars = [((left, 0), (left, volume), (right, volume), (right, 0)) for d, open, close, high, low, volume in quotes] sx = ax.figure.dpi * (1.0 / 72.0) # scale for points sy = ax.bbox.height / ax.viewLim.height barTransform = Affine2D().scale(sx, sy) dates = [d for d, open, close, high, low, volume in quotes] offsetsBars = [(d, 0) for d in dates] useAA = 0, # use tuple here lw = 0.5, # and here barCollection = PolyCollection( bars, facecolors=colors, edgecolors=((0, 0, 0, 1), ), antialiaseds=useAA, linewidths=lw, offsets=offsetsBars, transOffset=ax.transData, ) barCollection.set_transform(barTransform) minpy, maxx = (min(dates), max(dates)) miny = 0 maxy = max([volume for d, open, close, high, low, volume in quotes]) corners = (minpy, miny), (maxx, maxy) ax.update_datalim(corners) #print 'datalim', ax.dataLim.bounds #print 'viewlim', ax.viewLim.bounds ax.add_collection(barCollection) ax.autoscale_view() return barCollection
def _set_lim_and_transforms(self): # A (possibly non-linear) projection on the (already scaled) data # There are three important coordinate spaces going on here: # # 1. Data space: The space of the data itself # # 2. Axes space: The unit rectangle (0, 0) to (1, 1) # covering the entire plot area. # # 3. Display space: The coordinates of the resulting image, # often in pixels or dpi/inch. # This function makes heavy use of the Transform classes in # ``lib/matplotlib/transforms.py.`` For more information, see # the inline documentation there. # The goal of the first two transformations is to get from the # data space (in this case longitude and latitude) to axes # space. It is separated into a non-affine and affine part so # that the non-affine part does not have to be recomputed when # a simple affine change to the figure has been made (such as # resizing the window or changing the dpi). # 1) The core transformation from data space into # rectilinear space defined in the HammerTransform class. self.transProjection = self._get_core_transform(self.RESOLUTION) # 2) The above has an output range that is not in the unit # rectangle, so scale and translate it so it fits correctly # within the axes. The peculiar calculations of xscale and # yscale are specific to a Aitoff-Hammer projection, so don't # worry about them too much. self.transAffine = self._get_affine_transform() # 3) This is the transformation from axes space to display # space. self.transAxes = BboxTransformTo(self.bbox) # Now put these 3 transforms together -- from data all the way # to display coordinates. Using the '+' operator, these # transforms will be applied "in order". The transforms are # automatically simplified, if possible, by the underlying # transformation framework. self.transData = \ self.transProjection + \ self.transAffine + \ self.transAxes # The main data transformation is set up. Now deal with # gridlines and tick labels. # Longitude gridlines and ticklabels. The input to these # transforms are in display space in x and axes space in y. # Therefore, the input values will be in range (-xmin, 0), # (xmax, 1). The goal of these transforms is to go from that # space to display space. The tick labels will be offset 4 # pixels from the equator. self._xaxis_pretransform = \ Affine2D() \ .scale(1.0, self._longitude_cap * 2.0) \ .translate(0.0, -self._longitude_cap) self._xaxis_transform = \ self._xaxis_pretransform + \ self.transData self._xaxis_text1_transform = \ Affine2D().scale(1.0, 0.0) + \ self.transData + \ Affine2D().translate(0.0, 4.0) self._xaxis_text2_transform = \ Affine2D().scale(1.0, 0.0) + \ self.transData + \ Affine2D().translate(0.0, -4.0) # Now set up the transforms for the latitude ticks. The input to # these transforms are in axes space in x and display space in # y. Therefore, the input values will be in range (0, -ymin), # (1, ymax). The goal of these transforms is to go from that # space to display space. The tick labels will be offset 4 # pixels from the edge of the axes ellipse. yaxis_stretch = Affine2D().scale(np.pi * 2, 1).translate(-np.pi, 0) yaxis_space = Affine2D().scale(1.0, 1.1) self._yaxis_transform = \ yaxis_stretch + \ self.transData yaxis_text_base = \ yaxis_stretch + \ self.transProjection + \ (yaxis_space + self.transAffine + self.transAxes) self._yaxis_text1_transform = \ yaxis_text_base + \ Affine2D().translate(-8.0, 0.0) self._yaxis_text2_transform = \ yaxis_text_base + \ Affine2D().translate(8.0, 0.0)
def curved_earth_axes(rect=111, fig=None, minground=0., maxground=2000, minalt=0, maxalt=500, Re=6371., nyticks=5, nxticks=4): """ Create curved axes in ground-range and altitude Copied from DaViTPy: https://github.com/vtsuperdarn/davitpy/blob/1b578ea2491888e3d97d6e0a8bc6d8cc7c9211fb/davitpy/utils/plotUtils.py#L559 """ from matplotlib.transforms import Affine2D, Transform import mpl_toolkits.axisartist.floating_axes as floating_axes from matplotlib.projections import polar from mpl_toolkits.axisartist.grid_finder import FixedLocator, DictFormatter import numpy as np from pylab import gcf ang = maxground / Re minang = minground / Re angran = ang - minang angle_ticks = [(0, "{:.0f}".format(minground))] while angle_ticks[-1][0] < angran: tang = angle_ticks[-1][0] + 1. / nxticks * angran angle_ticks.append((tang, "{:.0f}".format((tang - minang) * Re))) grid_locator1 = FixedLocator([v for v, s in angle_ticks]) tick_formatter1 = DictFormatter(dict(angle_ticks)) altran = float(maxalt - minalt) alt_ticks = [(minalt + Re, "{:.0f}".format(minalt))] while alt_ticks[-1][0] < Re + maxalt: alt_ticks.append( (altran / float(nyticks) + alt_ticks[-1][0], "{:.0f}".format(altran / float(nyticks) + alt_ticks[-1][0] - Re))) _ = alt_ticks.pop() grid_locator2 = FixedLocator([v for v, s in alt_ticks]) tick_formatter2 = DictFormatter(dict(alt_ticks)) tr_rotate = Affine2D().rotate(np.pi / 2 - ang / 2) tr_shift = Affine2D().translate(0, Re) tr = polar.PolarTransform() + tr_rotate grid_helper = \ floating_axes.GridHelperCurveLinear(tr, extremes=(0, angran, Re+minalt, Re+maxalt), grid_locator1=grid_locator1, grid_locator2=grid_locator2, tick_formatter1=tick_formatter1, tick_formatter2=tick_formatter2,) if not fig: fig = gcf() ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper) # adjust axis # ax1.axis["left"].label.set_text(r"Alt. [km]") # ax1.axis["bottom"].label.set_text(r"Ground range [km]") ax1.invert_xaxis() ax1.minground = minground ax1.maxground = maxground ax1.minalt = minalt ax1.maxalt = maxalt ax1.Re = Re fig.add_subplot(ax1, transform=tr) # create a parasite axes whose transData in RA, cz aux_ax = ax1.get_aux_axes(tr) # for aux_ax to have a clip path as in ax aux_ax.patch = ax1.patch # but this has a side effect that the patch is drawn twice, and possibly # over some other artists. So, we decrease the zorder a bit to prevent this. ax1.patch.zorder = 0.9 return ax1, aux_ax
def draw_image(self, gc, x, y, im, transform=None): # docstring inherited h, w = im.shape[:2] if w == 0 or h == 0: return attrib = {} clipid = self._get_clip(gc) if clipid is not None: # Can't apply clip-path directly to the image because the # image has a transformation, which would also be applied # to the clip-path self.writer.start('g', attrib={'clip-path': 'url(#%s)' % clipid}) oid = gc.get_gid() url = gc.get_url() if url is not None: self.writer.start('a', attrib={'xlink:href': url}) if rcParams['svg.image_inline']: bytesio = io.BytesIO() _png.write_png(im, bytesio) oid = oid or self._make_id('image', bytesio.getvalue()) attrib['xlink:href'] = ( "data:image/png;base64,\n" + base64.b64encode(bytesio.getvalue()).decode('ascii')) else: self._imaged[self.basename] = (self._imaged.get(self.basename, 0) + 1) filename = '%s.image%d.png' % (self.basename, self._imaged[self.basename]) _log.info('Writing image file for inclusion: %s', filename) _png.write_png(im, filename) oid = oid or 'Im_' + self._make_id('image', filename) attrib['xlink:href'] = filename attrib['id'] = oid if transform is None: w = 72.0 * w / self.image_dpi h = 72.0 * h / self.image_dpi self.writer.element('image', transform=generate_transform([ ('scale', (1, -1)), ('translate', (0, -h)) ]), x=short_float_fmt(x), y=short_float_fmt(-(self.height - y - h)), width=short_float_fmt(w), height=short_float_fmt(h), attrib=attrib) else: alpha = gc.get_alpha() if alpha != 1.0: attrib['opacity'] = short_float_fmt(alpha) flipped = (Affine2D().scale(1.0 / w, 1.0 / h) + transform + Affine2D().translate(x, y).scale(1.0, -1.0).translate( 0.0, self.height)) attrib['transform'] = generate_transform([('matrix', flipped.frozen())]) self.writer.element('image', width=short_float_fmt(w), height=short_float_fmt(h), attrib=attrib) if url is not None: self.writer.end('a') if clipid is not None: self.writer.end('g')
def get_slice_axes(fig, num, R, Rb, gs=None, lat1=-90.0, lat2=90.0, shrink=1.0): tr_rotate = Affine2D().translate(0, 90) # set up polar axis tr = PolarAxes.PolarTransform() #+ tr_rotate angle_ticks = [(np.radians(i), str(i)) for i in np.arange(80, -81, -10)] # angle_ticks = [(np.radians(80), r"$80$"), # (np.radians(40), r"$\frac{3}{4}\pi$"), # (1.*np.pi, r"$\pi$"), # (1.25*np.pi, r"$\frac{5}{4}\pi$"), # (1.5*np.pi, r"$\frac{3}{2}\pi$"), # (1.75*np.pi, r"$\frac{7}{4}\pi$")] # set up ticks and spacing around the circle grid_locator1 = FixedLocator([v for v, s in angle_ticks]) tick_formatter1 = DictFormatter(dict(angle_ticks)) # set up grid spacing along the 'radius' radius_ticks = [ (Rb, ''), # (1.5, '%i' % (MAX_R/2.)), (R, '') ] grid_locator2 = FixedLocator([v for v, s in radius_ticks]) tick_formatter2 = DictFormatter(dict(radius_ticks)) # define angle ticks around the circumference: # set up axis: # tr: the polar axis setup # extremes: theta max, theta min, r max, r min # the grid for the theta axis # the grid for the r axis # the tick formatting for the theta axis # the tick formatting for the r axis l1 = np.radians(lat1) l2 = np.radians(lat2) grid_helper = floating_axes.GridHelperCurveLinear( tr, extremes=(l1, l2, R, Rb), grid_locator1=grid_locator1, grid_locator2=grid_locator2, tick_formatter1=tick_formatter1, tick_formatter2=tick_formatter2) if type(num) is tuple: ax1 = floating_axes.FloatingSubplot(fig, num[0], num[1], num[2], grid_helper=grid_helper) else: if gs is not None: ax1 = floating_axes.FloatingSubplot(fig, gs, grid_helper=grid_helper) else: ax1 = floating_axes.FloatingSubplot(fig, num, grid_helper=grid_helper) fig.add_subplot(ax1) ax1.axis["left"].set_axis_direction("bottom") ax1.axis["right"].set_axis_direction("bottom") ax1.axis["left"].major_ticklabels.set_rotation(90) ax1.axis["bottom"].set_axis_direction("right") ax1.axis["bottom"].major_ticks.set_tick_out(True) # ax1.axis["bottom"].major_ticks.set_ticks([]) majortick_iter, minortick_iter = ax1.axis[ 'bottom']._axis_artist_helper.get_tick_iterators(ax1) tick_loc_angle, ticklabel_loc_angle_label = ax1.axis[ 'bottom']._get_tick_info(majortick_iter) # ax1.axis["bottom"].toggle(all=False)#, ticks=True) ax1.axis["top"].toggle(all=False) #, ticks=True) ax1.axis["left"].toggle(all=False) #, ticks=True) ax1.axis["right"].toggle(all=False) #, ticks=True) aux_ax = ax1.get_aux_axes(tr) aux_ax.patch = ax1.patch # for aux_ax to have a clip path as in ax ax1.patch.zorder = 0.9 # but this has a side effect that the patch is # drawn twice, and possibly over some other # artists. So, we decrease the zorder a bit to # prevent this. return ax1, aux_ax
def curvedEarthAxes(rect=111, fig=None, minground=0., maxground=2000, minalt=0, maxalt=500, Re=6371., nyticks=5, nxticks=4): """Create curved axes in ground-range and altitude Parameters ---------- rect : Optional[int] subplot spcification fig : Optional[pylab.figure object] (default to gcf) minground : Optional[float] maxground : Optional[int] maximum ground range [km] minalt : Optional[int] lowest altitude limit [km] maxalt : Optional[int] highest altitude limit [km] Re : Optional[float] Earth radius in kilometers nyticks : Optional[int] Number of y axis tick marks; default is 5 nxticks : Optional[int] Number of x axis tick marks; deafult is 4 Returns ------- ax : matplotlib.axes object containing formatting aax : matplotlib.axes object containing data Example ------- import numpy as np from utils import plotUtils ax, aax = plotUtils.curvedEarthAxes() th = np.linspace(0, ax.maxground/ax.Re, 50) r = np.linspace(ax.Re+ax.minalt, ax.Re+ax.maxalt, 20) Z = exp( -(r - 300 - ax.Re)**2 / 100**2 ) * np.cos(th[:, np.newaxis]/th.max()*4*np.pi) x, y = np.meshgrid(th, r) im = aax.pcolormesh(x, y, Z.T) ax.grid() written by Sebastien, 2013-04 """ from matplotlib.transforms import Affine2D, Transform import mpl_toolkits.axisartist.floating_axes as floating_axes from matplotlib.projections import polar from mpl_toolkits.axisartist.grid_finder import FixedLocator, DictFormatter import numpy as np from pylab import gcf ang = maxground / Re minang = minground / Re angran = ang - minang angle_ticks = [(0, "{:.0f}".format(minground))] while angle_ticks[-1][0] < angran: tang = angle_ticks[-1][0] + 1. / nxticks * angran angle_ticks.append((tang, "{:.0f}".format((tang - minang) * Re))) grid_locator1 = FixedLocator([v for v, s in angle_ticks]) tick_formatter1 = DictFormatter(dict(angle_ticks)) altran = float(maxalt - minalt) alt_ticks = [(minalt + Re, "{:.0f}".format(minalt))] while alt_ticks[-1][0] < Re + maxalt: alt_ticks.append( (altran / float(nyticks) + alt_ticks[-1][0], "{:.0f}".format(altran / float(nyticks) + alt_ticks[-1][0] - Re))) _ = alt_ticks.pop() grid_locator2 = FixedLocator([v for v, s in alt_ticks]) tick_formatter2 = DictFormatter(dict(alt_ticks)) tr_rotate = Affine2D().rotate(np.pi / 2 - ang / 2) tr_shift = Affine2D().translate(0, Re) tr = polar.PolarTransform() + tr_rotate grid_helper = \ floating_axes.GridHelperCurveLinear(tr, extremes=(0, angran, Re+minalt, Re+maxalt), grid_locator1=grid_locator1, grid_locator2=grid_locator2, tick_formatter1=tick_formatter1, tick_formatter2=tick_formatter2,) if not fig: fig = gcf() ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper) # adjust axis ax1.axis["left"].label.set_text(r"Alt. [km]") ax1.axis["bottom"].label.set_text(r"Ground range [km]") ax1.invert_xaxis() ax1.minground = minground ax1.maxground = maxground ax1.minalt = minalt ax1.maxalt = maxalt ax1.Re = Re fig.add_subplot(ax1, transform=tr) # create a parasite axes whose transData in RA, cz aux_ax = ax1.get_aux_axes(tr) # for aux_ax to have a clip path as in ax aux_ax.patch = ax1.patch # but this has a side effect that the patch is drawn twice, and possibly # over some other artists. So, we decrease the zorder a bit to prevent this. ax1.patch.zorder = 0.9 return ax1, aux_ax
def draw_rink(is_horizontal=True, x_range=None, y_range=None, rink_length=None): """ Draw a plot of an NHL ice surface. Plotting is based on the NHL coordinate system which goes from -100 to 100 on the x-axis and -42.5 to 42.5 on the y-axis. Args: is_horizontal: bool; default=True Indicates whether to draw the rink horizontally (left and right as end boards) or vertically (top and bottom as end boards). When is_horizontal is False, x,y-coordinates will have to be reversed (x,y => y,x) when adding to the plot. x_range: "half", "ozone", float, or list; default=None Lower and upper bounds of the default display on the x-axis. The entire rink will be drawn regardless, this only controls what section of the rink's length is initially shown. Coordinates can range from -100 to 100. "half": bounds set to 0 and 100. "ozone": bounds set to 25 and 100. float: value will be used as the lower bound with 100 as the upper bound. list: will attempt to use the first element as the lower bound, the second as the upper bound. If none of the above are provided, the bounds will be set to -100 and 100. y_range: "half", float or list; default=None Lower and upper bounds of the default display on the y-axis. The entire rink will be drawn regardless, this only controls what section of the rink's width is initially shown. Coordinates can range from -42.5 to 42.5. "half": bounds set to 0 and 42.5. float: value will be used as the lower bound with 42.5 as the upper bound. list: will attempt to use the first element as the lower bound, the second as the upper bound. If none of the above are provided, the bounds will bet set to -42.5 and 42.5. rink_length: float; default=None Length of the rink (end board to end board) for plotting. If None, will use default of 14 if using a full horizontal rink or 8 otherwise. Width is set automatically based on rink_length and dimensions used. Returns: matplotlib Axes. Axes for the rink plot. """ if x_range in ("half", "ozone"): x_range = [0 if x_range == "half" else 25, 100] elif isinstance(x_range, (int, float)): x_range = [x_range, 100] elif isinstance(x_range, list) and x_range: x_range.append(100) x_range = x_range[:2] else: x_range = [-100, 100] if x_range[0] > x_range[1]: x_range = x_range[::-1] x_range = [max(-100, x_range[0]), min(100, x_range[1])] if x_range[0] >= 100: x_range = [-100, 100] if y_range == "half": y_range = [0, 42.5] elif isinstance(y_range, (int, float)): y_range = [y_range, 42.5] elif isinstance(y_range, list) and y_range: y_range.append(42.5) y_range = y_range[:2] else: y_range = [-42.5, 42.5] if y_range[0] > y_range[1]: y_range = y_range[::-1] y_range = [max(-42.5, y_range[0]), min(100, y_range[1])] if y_range[0] >= 42.5: y_range = [-42.5, 42.5] delta_x = x_range[1] - x_range[0] delta_y = y_range[1] - y_range[0] if rink_length is None: rink_length = 14 if is_horizontal and delta_x == 200 else 8 length = rink_length width = rink_length * delta_y / delta_x if not is_horizontal: length, width = width, length plt.figure(figsize=(length, width)) ax = plt.gca() ax.set_aspect("equal") patches = [] # red line # 1' in width # all other red lines are 2" in width, but will be drawn thicker patches.append(mpatches.Rectangle((-0.5, -42.5), 1, 85, color="red", zorder=0)) # center faceoff patches.append(mpatches.Circle((0, 0), .5, color="blue", fill=True, zorder=0)) # center circle patches.append(mpatches.Circle((0, 0), 15, color="red", fill=False, zorder=0)) # ref half-circle # 10' radius patches.append(mpatches.Arc((0, -42.5), 20, 20, theta1=0, theta2=180, color="red", zorder=0)) # 5'7" between, 5'7" = 67", divide by 12 to get feet, divide by 2 to get half on each side between_hashmarks = 67 / 24 # edge of the circle hashmark_edge = (15**2 - between_hashmarks**2)**.5 for side in (-1, 1): # blue lines # 1' in width # neutral zone is 50', half on each side = 25 patches.append(mpatches.Rectangle((25 * side, -42.5), side, 85, color="blue", zorder=0)) for y in (-22, 22): # faceoff dots # 2' diameter # offensive zone dots are 20' from the goal line with 44' in between patches.append(mpatches.Circle((69 * side, y), 1, color="red", fill=True, zorder=0)) # neutral zone dots # 5' from the bluelines (25 - 5 = 20), 44' in between patches.append(mpatches.Circle((20 * side, y), 1, color="red", fill=True, zorder=0)) # faceoff circles # 15' radius patches.append(mpatches.Circle((69 * side, y), 15, color="red", fill=False, zorder=0)) for circle_side in (-1, 1): # faceoff lines # 4' length, 3' width ax.plot((69 * side * circle_side + 2 * side, 69 * circle_side * side + 6 * side), (y + 1.75, y + 1.75), "red", zorder=0) ax.plot((69 * side * circle_side + 2 * side, 69 * circle_side * side + 6 * side), (y - 1.75, y - 1.75), "red", zorder=0) ax.plot((69 * side * circle_side + 2 * side, 69 * circle_side * side + 2 * side), (y + 1.75, y + 4.75), "red", zorder=0) ax.plot((69 * side * circle_side + 2 * side, 69 * circle_side * side + 2 * side), (y - 1.75, y - 4.75), "red", zorder=0) # hashmarks ax.plot((69 * side * circle_side + between_hashmarks * side, 69 * side * circle_side + between_hashmarks * side), (y - hashmark_edge, y - hashmark_edge - 2), "red", zorder=0) ax.plot((69 * side * circle_side + between_hashmarks * side, 69 * side * circle_side + between_hashmarks * side), (y + hashmark_edge, y + hashmark_edge + 2), "red", zorder=0) # nets # depth is 40" with 18" radius (NHL rulebook says 20", but supposed to be 18") # width of posts is 19/8", total width is 88" # almost certainly incorrect, but close enough patches.append(mpatches.Circle(((89 + 11/6) * side, 3 - 5/6), 18/12, color="grey", zorder=2)) patches.append(mpatches.Circle(((89 + 11/6) * side, 5/6 - 3), 18/12, color="grey", zorder=2)) patches.append(mpatches.Polygon( [[89 * side, 3 + 19 / 8 / 12], [(89 + 11/6) * side, 88 / 24], [(89 + 40 / 12) * side, 3 - 5/6], [(89 + 40 / 12) * side, 5 / 6 - 3], [(89 + 11 / 6) * side, -88 / 24], [89 * side, -3 - 19 / 8 / 12]], color="grey", zorder=2)) # creases # rectangle extends 4'6" out, then ellipse of width 2' patches.append(mpatches.Rectangle((89*side, -4), 4.5*-side, 8, color="lightblue", zorder=-1)) patches.append(arc_patch((84.5*side, 0), 2, 4, 270-180*side, 270, fill=True, color="lightblue", zorder=-1)) # outline of crease ax.plot((89*side, 84.5*side), (-4, -4), "red", zorder=1) ax.plot((89*side, 84.5*side), (4, 4), "red", zorder=1) patches.append(mpatches.Arc((84.5*side, 0), 4, 8, theta1=90*side, theta2=270*side, color="red", zorder=1)) # restricted zone # 8' from the post to 11' from the post (posts are 3' from center) ax.plot((89*side, 100*side), (-11, -14), "red", zorder=0) ax.plot((89*side, 100*side), (11, 14), "red", zorder=0) # curves at end of boards # arc of a circle with 28' radius patches.append(mpatches.Arc(((100 - 28) * side, 42.5 - 28), 56, 56, theta1=45 - 45 * side, theta2=135 - 45 * side, color="black", zorder=2)) patches.append(mpatches.Arc(((100 - 28) * side, -42.5 + 28), 56, 56, theta1=225 + 45 * side, theta2=135 - 135 * side, color="black", zorder=2)) # side boards ax.plot((100 - 28, 28 - 100), (42.5 * side, 42.5 * side), "black", zorder=2) # end boards ax.plot((100 * side, 100 * side), (42.5 - 28, 28 - 42.5), "black", zorder=2) # goal lines # 11' from end boards # boards curl in arc of circle with 28' radius # distance from center of circle to side boards = 28' # distance from center of circle to goal line = 28' - 11' # distance from center of ice (on y-axis) to center of circle = 42.5 - 28 end_board_y = np.sqrt(28 ** 2 - (28 - 11) ** 2) + (42.5 - 28) ax.plot((89 * side, 89 * side), (-end_board_y, end_board_y), "red", zorder=1) ax.tick_params(axis="both", which="both", bottom=False, left=False, labelbottom=False, labelleft=False) # rotate everything 90 degrees if displaying rink vertically trans = Affine2D().rotate_deg(0 if is_horizontal else 90) + ax.transData for patch in patches: patch.set_transform(trans) ax.add_patch(patch) for line in ax.lines: line.set_transform(trans) if not is_horizontal: old = ax.axis() ax.axis(old[2:4] + old[0:2]) x_range, y_range = y_range, x_range # only display the specified region of the rink ax.set_xlim(*x_range) ax.set_ylim(*y_range) if not is_horizontal: ax.invert_yaxis() return ax
def test_axis_direction(): fig = plt.figure(figsize=(5, 5)) # PolarAxes.PolarTransform takes radian. However, we want our coordinate # system in degree tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() # polar projection, which involves cycle, and also has limits in # its coordinates, needs a special method to find the extremes # (min, max of the coordinate within the view). # 20, 20 : number of sampling points along x, y direction extreme_finder = angle_helper.ExtremeFinderCycle( 20, 20, lon_cycle=360, lat_cycle=None, lon_minmax=None, lat_minmax=(0, np.inf), ) grid_locator1 = angle_helper.LocatorDMS(12) tick_formatter1 = angle_helper.FormatterDMS() grid_helper = GridHelperCurveLinear(tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, tick_formatter1=tick_formatter1) ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper) for axis in ax1.axis.values(): axis.set_visible(False) fig.add_subplot(ax1) ax1.axis["lat1"] = axis = grid_helper.new_floating_axis( 0, 130, axes=ax1, axis_direction="left") axis.label.set_text("Test") axis.label.set_visible(True) axis.get_helper()._extremes = 0.001, 10 ax1.axis["lat2"] = axis = grid_helper.new_floating_axis( 0, 50, axes=ax1, axis_direction="right") axis.label.set_text("Test") axis.label.set_visible(True) axis.get_helper()._extremes = 0.001, 10 ax1.axis["lon"] = axis = grid_helper.new_floating_axis( 1, 10, axes=ax1, axis_direction="bottom") axis.label.set_text("Test 2") axis.get_helper()._extremes = 50, 130 axis.major_ticklabels.set_axis_direction("top") axis.label.set_axis_direction("top") grid_helper.grid_finder.grid_locator1.den = 5 grid_helper.grid_finder.grid_locator2._nbins = 5 ax1.set_aspect(1.) ax1.set_xlim(-8, 8) ax1.set_ylim(-4, 12) ax1.grid(True)
def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, unsampled=False, round_to_pixel_border=True): """ Normalize, rescale and color the image `A` from the given in_bbox (in data space), to the given out_bbox (in pixel space) clipped to the given clip_bbox (also in pixel space), and magnified by the magnification factor. `A` may be a greyscale image (MxN) with a dtype of `float32`, `float64`, `uint16` or `uint8`, or an RGBA image (MxNx4) with a dtype of `float32`, `float64`, or `uint8`. If `unsampled` is True, the image will not be scaled, but an appropriate affine transformation will be returned instead. If `round_to_pixel_border` is True, the output image size will be rounded to the nearest pixel boundary. This makes the images align correctly with the axes. It should not be used in cases where you want exact scaling, however, such as FigureImage. Returns the resulting (image, x, y, trans), where (x, y) is the upper left corner of the result in pixel space, and `trans` is the affine transformation from the image to pixel space. """ if A is None: raise RuntimeError('You must first set the image' ' array or the image attribute') clipped_bbox = Bbox.intersection(out_bbox, clip_bbox) if clipped_bbox is None: return None, 0, 0, None out_width_base = clipped_bbox.width * magnification out_height_base = clipped_bbox.height * magnification if out_width_base == 0 or out_height_base == 0: return None, 0, 0, None if self.origin == 'upper': # Flip the input image using a transform. This avoids the # problem with flipping the array, which results in a copy # when it is converted to contiguous in the C wrapper t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1) else: t0 = IdentityTransform() t0 += (Affine2D().scale(in_bbox.width / A.shape[1], in_bbox.height / A.shape[0]).translate(in_bbox.x0, in_bbox.y0) + self.get_transform()) t = (t0 + Affine2D().translate(-clipped_bbox.x0, -clipped_bbox.y0).scale( magnification, magnification)) # So that the image is aligned with the edge of the axes, we want # to round up the output width to the next integer. This also # means scaling the transform just slightly to account for the # extra subpixel. if (t.is_affine and round_to_pixel_border and (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)): out_width = int(ceil(out_width_base)) out_height = int(ceil(out_height_base)) extra_width = (out_width - out_width_base) / out_width_base extra_height = (out_height - out_height_base) / out_height_base t += Affine2D().scale(1.0 + extra_width, 1.0 + extra_height) else: out_width = int(out_width_base) out_height = int(out_height_base) if not unsampled: created_rgba_mask = False if A.ndim not in (2, 3): raise ValueError("Invalid dimensions, got %s" % (A.shape, )) if A.ndim == 2: A = self.norm(A) if A.dtype.kind == 'f': # If the image is greyscale, convert to RGBA and # use the extra channels for resizing the over, # under, and bad pixels. This is needed because # Agg's resampler is very aggressive about # clipping to [0, 1] and we use out-of-bounds # values to carry the over/under/bad information rgba = np.empty((A.shape[0], A.shape[1], 4), dtype=A.dtype) rgba[..., 0] = A # normalized data rgba[..., 1] = A < 0 # under data rgba[..., 2] = A > 1 # over data rgba[..., 3] = ~A.mask # bad data A = rgba output = np.zeros((out_height, out_width, 4), dtype=A.dtype) alpha = 1.0 created_rgba_mask = True else: # colormap norms that output integers (ex NoNorm # and BoundaryNorm) to RGBA space before # interpolating. This is needed due to the # Agg resampler only working on floats in the # range [0, 1] and because interpolating indexes # into an arbitrary LUT may be problematic. # # This falls back to interpolating in RGBA space which # can produce it's own artifacts of colors not in the map # showing up in the final image. A = self.cmap(A, alpha=self.get_alpha(), bytes=True) if not created_rgba_mask: # Always convert to RGBA, even if only RGB input if A.shape[2] == 3: A = _rgb_to_rgba(A) elif A.shape[2] != 4: raise ValueError("Invalid dimensions, got %s" % (A.shape, )) output = np.zeros((out_height, out_width, 4), dtype=A.dtype) alpha = self.get_alpha() if alpha is None: alpha = 1.0 _image.resample(A, output, t, _interpd_[self.get_interpolation()], self.get_resample(), alpha, self.get_filternorm() or 0.0, self.get_filterrad() or 0.0) if created_rgba_mask: # Convert back to a masked greyscale array so # colormapping works correctly hid_output = output output = np.ma.masked_array(hid_output[..., 0], hid_output[..., 3] < 0.5) # relabel under data output[hid_output[..., 1] > .5] = -1 # relabel over data output[hid_output[..., 2] > .5] = 2 output = self.to_rgba(output, bytes=True, norm=False) # Apply alpha *after* if the input was greyscale without a mask if A.ndim == 2 or created_rgba_mask: alpha = self.get_alpha() if alpha is not None and alpha != 1.0: alpha_channel = output[:, :, 3] alpha_channel[:] = np.asarray( np.asarray(alpha_channel, np.float32) * alpha, np.uint8) else: if self._imcache is None: self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2)) output = self._imcache # Subset the input image to only the part that will be # displayed subset = TransformedBbox(clip_bbox, t0.frozen().inverted()).frozen() output = output[int(max(subset.ymin, 0) ):int(min(subset.ymax + 1, output.shape[0])), int(max(subset.xmin, 0) ):int(min(subset.xmax + 1, output.shape[1]))] t = Affine2D().translate(int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t return output, clipped_bbox.x0, clipped_bbox.y0, t
def _label(self, colors): """ Method to create an edges label :return: """ its_label = '{:g}'.format( abs(self.edge.capacity) if self.edge.capacity is not None else '') status_color = colors['bc'] if not self.layout_mode: in_percent = int( round(100 * self.edge.flux / float(self.edge.capacity), 0)) if in_percent < 100: status_color = colors['wc'] its_label += '; {:g}%'.format(in_percent) # get the label coordinates based on self.segments (use the end of the first segment if several segments are # present, if it is a single, place the label half the way s_coords, e_coords = self.segments[0] edge_vec = Vec(s_coords, e_coords) start_vec = Vec(s_coords) # maybe always use the last part... if len(self.segments) == 1: # single segment: place it half way part_way_vec = start_vec + self.label_position * edge_vec coords_label = part_way_vec.coords[:2] else: # several segments: place label at the end of the first #half_way_vec = start_vec + 0.5 * edge_vec #coords_label = half_way_vec.coords[:2] coords_label = e_coords if self.with_label: self.labels = [ # so far there is just a single label for an edge (its_label, coords_label, { 'horizontalalignment': 'center', 'verticalalignment': 'center', 'size': self.scale * 200 * self.label_scale, 'color': colors['tc'], 'box_fc': status_color }) ] else: self.labels = None # cross through the label if it is not functional if not self.functional: cross_bar = FancyBboxPatch( (0, 0), self.scale * self.label_scale, 0.07 * self.scale * self.label_scale, boxstyle="square,pad={}".format(0.05 * self.scale * self.label_scale), color=colors['ac'], alpha=min(1, 2 * self.alpha), zorder=6) rotator = Affine2D().rotate_deg(-45) corner_coords = (coords_label[0] - 0.4 * self.scale * self.label_scale, coords_label[1] + 0.3 * self.scale * self.label_scale) translator = Affine2D().translate(*corner_coords) transform = rotator + translator cross_bar.set_transform(transform) self.super_patch_collection.append(cross_bar)
def plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize=4, colorup='k', colordown='r'): """Represent the time, open, high, low, close as a vertical line ranging from low to high. The left tick is the open and the right tick is the close. *opens*, *highs*, *lows* and *closes* must have the same length. NOTE: this code assumes if any value open, high, low, close is missing (*-1*) they all are missing Parameters ---------- ax : `Axes` an Axes instance to plot to opens : sequence sequence of opening values highs : sequence sequence of high values lows : sequence sequence of low values closes : sequence sequence of closing values ticksize : int size of open and close ticks in points colorup : color the color of the lines where close >= open colordown : color the color of the lines where close < open Returns ------- ret : list a list of lines added to the axes """ _check_input(opens, highs, lows, closes) rangeSegments = [((i, low), (i, high)) for i, low, high in zip(xrange(len(lows)), lows, highs) if low != -1] # the ticks will be from ticksize to 0 in points at the origin and # we'll translate these to the i, close location openSegments = [((-ticksize, 0), (0, 0))] # the ticks will be from 0 to ticksize in points at the origin and # we'll translate these to the i, close location closeSegments = [((0, 0), (ticksize, 0))] offsetsOpen = [(i, open) for i, open in zip(xrange(len(opens)), opens) if open != -1] offsetsClose = [(i, close) for i, close in zip(xrange(len(closes)), closes) if close != -1] scale = ax.figure.dpi * (1.0 / 72.0) tickTransform = Affine2D().scale(scale, 0.0) colorup = mcolors.to_rgba(colorup) colordown = mcolors.to_rgba(colordown) colord = {True: colorup, False: colordown} colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1] useAA = 0, # use tuple here lw = 1, # and here rangeCollection = LineCollection(rangeSegments, colors=colors, linewidths=lw, antialiaseds=useAA, ) openCollection = LineCollection(openSegments, colors=colors, antialiaseds=useAA, linewidths=lw, offsets=offsetsOpen, transOffset=ax.transData, ) openCollection.set_transform(tickTransform) closeCollection = LineCollection(closeSegments, colors=colors, antialiaseds=useAA, linewidths=lw, offsets=offsetsClose, transOffset=ax.transData, ) closeCollection.set_transform(tickTransform) minpy, maxx = (0, len(rangeSegments)) miny = min([low for low in lows if low != -1]) maxy = max([high for high in highs if high != -1]) corners = (minpy, miny), (maxx, maxy) ax.update_datalim(corners) ax.autoscale_view() # add these last ax.add_collection(rangeCollection) ax.add_collection(openCollection) ax.add_collection(closeCollection) return rangeCollection, openCollection, closeCollection
def _basic_outline(self, colors): if self.need != 0: #center = Circle( # self.coords, # self.r / float(20), # color=color['lc'], zorder=4, facecolor=color['lc'], fill=True, alpha=self.alpha #) if self.layout_mode: border = Circle(self.coords, 0.99 * self.r, color=colors['lc'], zorder=3, fill=False, edgecolor=colors['lc'], alpha=self.alpha, linewidth=70 * self.scale) role_fill = Circle(self.coords, self.r, color=self.role_color, zorder=4, facecolor=self.role_color, fill=True, alpha=0.5 * self.alpha) self.basic_structure = [role_fill, border] else: border = Circle(self.coords, self.r, color=colors['lc'], zorder=3, fill=False, edgecolor=colors['lc'], alpha=self.alpha, linewidth=0.5) self.basic_structure = [ border, ] #center] else: center = Circle(self.coords, self.r / self._transit_fraction, color=colors['lc'], zorder=4, facecolor=colors['lc'], fill=True, alpha=self.alpha) self.basic_structure = [center] if not self.functional: size = self.r if self.need != 0. else self.r / self._transit_fraction cross_bar = FancyBboxPatch( (0, 0), 2.4 * size, 0.2 * size, boxstyle="square,pad={}".format(0.05 * self.scale * self.label_scale), color=colors['ac'], alpha=min(1, 2 * self.alpha), zorder=6) rotator = Affine2D().rotate_deg(-40) corner_coords = (self.coords[0] - size, self.coords[1] + 0.7 * size) translator = Affine2D().translate(*corner_coords) transform = rotator + translator cross_bar.set_transform(transform) self.super_patch_collection.append(cross_bar)
def plot_polar_heatmap(data, name, interp_factor=5., color_limits=False, hide_colorbar=False, vmin=None, vmax=None, log_scale=True, dpi=200, output_dir=None): """Plots the polar heatmap describing azimuth and latitude / elevation components. Plots the polar heatmap where each cell of the heatmap corresponds to the specific element of the array provided by `gather_polar_errors` function. Parameters ---------- data : 2D array Indicates the array containing the sum of angular errors within the specified angular ranges. It is usually provided by `gather_polar_errors` function. name : str Indicates the name of the output png file. interp_factor : float Indicates the interpolation factor of the heatmap. color_limits : boolean Specifies if the determined intensity limits should be returned. hide_colorbar : boolean Specifies if the colorbar should be hidden. vmin : float Indicates the minimum value of the colorbar. vmax : float Indicates the maximum value of the colorbar. log_scale : float Specifies if the heatmap sould be in the logarithmic scale. dpi : integer Indicates the DPI of the output image. output_dir : str Indicates the path to the output folder where the image will be stored. """ th0, th1 = 0., 180. r0, r1 = 0, 90 thlabel, rlabel = 'Azimuth', 'Elevation' tr_scale = Affine2D().scale(np.pi / 180., 1.) tr = tr_scale + PolarAxes.PolarTransform() lat_ticks = [(.0 * 90., '0$^{\circ}$'), (.33 * 90., '30$^{\circ}$'), (.66 * 90., '60$^{\circ}$'), (1. * 90., '90$^{\circ}$')] r_grid_locator = FixedLocator([v for v, s in lat_ticks]) r_grid_formatter = DictFormatter(dict(lat_ticks)) angle_ticks = [(0 * 180., '90$^{\circ}$'), (.25 * 180., '45$^{\circ}$'), (.5 * 180., '0$^{\circ}$'), (.75 * 180., '-45$^{\circ}$'), (1. * 180., '-90$^{\circ}$')] theta_grid_locator = FixedLocator([v for v, s in angle_ticks]) theta_tick_formatter = DictFormatter(dict(angle_ticks)) grid_helper = GridHelperCurveLinear(tr, extremes=(th0, th1, r0, r1), grid_locator1=theta_grid_locator, grid_locator2=r_grid_locator, tick_formatter1=theta_tick_formatter, tick_formatter2=r_grid_formatter) fig = plt.figure() ax = floating_axes.FloatingSubplot(fig, 111, grid_helper=grid_helper) fig.add_subplot(ax) ax.set_facecolor('white') ax.axis["bottom"].set_visible(False) ax.axis["top"].toggle(ticklabels=True, label=True) ax.axis["top"].set_axis_direction("bottom") ax.axis["top"].major_ticklabels.set_axis_direction("top") ax.axis["top"].label.set_axis_direction("top") ax.axis["left"].set_axis_direction("bottom") ax.axis["right"].set_axis_direction("top") ax.axis["top"].label.set_text(thlabel) ax.axis["left"].label.set_text(rlabel) aux_ax = ax.get_aux_axes(tr) aux_ax.patch = ax.patch ax.patch.zorder = 0.9 rad = np.linspace(0, 90, data.shape[1]) azm = np.linspace(0, 180, data.shape[0]) f = interpolate.interp2d(rad, azm, data, kind='linear', bounds_error=True, fill_value=0) new_rad = np.linspace(0, 90, 180 * interp_factor) new_azm = np.linspace(0, 180, 360 * interp_factor) new_data_angle_dist = f(new_rad, new_azm) new_r, new_th = np.meshgrid(new_rad, new_azm) new_data_angle_dist += 1. if log_scale: data_mesh = aux_ax.pcolormesh( new_th, new_r, new_data_angle_dist, cmap='jet', norm=colors.LogNorm( vmin=1. if vmin is None else vmin, vmax=new_data_angle_dist.max() if vmax is None else vmax)) else: data_mesh = aux_ax.pcolormesh(new_th, new_r, new_data_angle_dist, cmap='jet', vmin=vmin, vmax=vmax) cbar = plt.colorbar(data_mesh, orientation='vertical', shrink=.88, pad=.1, aspect=15) cbar.ax.set_ylabel('Absolute error, [deg.]') if hide_colorbar: cbar.remove() ax.grid(False) plt.show() if output_dir is not None: if not os.path.exists(output_dir): os.makedirs(output_dir) fig.savefig(os.path.join(output_dir, '{}_chart.png'.format(name)), transparent=False, bbox_inches='tight', pad_inches=0.1, dpi=dpi) if color_limits: return 1., new_data_angle_dist.max()
def _get_transformed_clip_path(self): x, y, width, height = self.state.clipping_path rect = ((x, y), (x+width, y), (x+width, y+height), (x, y+height)) transform = Affine2D.from_values(*affine.affine_params(self.get_ctm())) return TransformedPath(Path(rect), transform)
def curvelinear_test3(fig): """ polar projection, but in a rectangular box. """ global ax1, axis import numpy as np import angle_helper from matplotlib.projections import PolarAxes # PolarAxes.PolarTransform takes radian. However, we want our coordinate # system in degree tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() # polar projection, which involves cycle, and also has limits in # its coordinates, needs a special method to find the extremes # (min, max of the coordinate within the view). grid_locator1 = angle_helper.LocatorDMS(15) # Find a grid values appropriate for the coordinate (degree, # minute, second). tick_formatter1 = angle_helper.FormatterDMS() # And also uses an appropriate formatter. Note that,the # acceptable Locator and Formatter class is a bit different than # that of mpl's, and you cannot directly use mpl's Locator and # Formatter here (but may be possible in the future). from grid_finder import FixedLocator grid_locator2 = FixedLocator([2, 4, 6, 8, 10]) grid_helper = GridHelperCurveLinear( tr, extremes=(0, 360, 10, 3), grid_locator1=grid_locator1, grid_locator2=grid_locator2, tick_formatter1=tick_formatter1, tick_formatter2=None, ) ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper) #ax1.axis["top"].set_visible(False) #ax1.axis["bottom"].major_ticklabels.set_axis_direction("top") fig.add_subplot(ax1) #ax1.grid(True) r_scale = 10. tr2 = Affine2D().scale(1., 1. / r_scale) + tr grid_locator2 = FixedLocator([30, 60, 90]) grid_helper2 = GridHelperCurveLinear( tr2, extremes=(0, 360, 10. * r_scale, 3. * r_scale), grid_locator2=grid_locator2, ) ax1.axis["right"] = axis = grid_helper2.new_fixed_axis("right", axes=ax1) ax1.axis["left"].label.set_text("Test 1") ax1.axis["right"].label.set_text("Test 2") for an in ["left", "right"]: ax1.axis[an].set_visible(False) #grid_helper2 = ax1.get_grid_helper() ax1.axis["z"] = axis = grid_helper.new_floating_axis( 1, 7, axes=ax1, axis_direction="bottom") axis.toggle(all=True, label=True) #axis.label.set_axis_direction("top") axis.label.set_text("z = ?") axis.label.set_visible(True) axis.line.set_color("0.5") #axis.label.set_visible(True) ax2 = ax1.get_aux_axes(tr) xx, yy = [67, 90, 75, 30], [2, 5, 8, 4] ax2.scatter(xx, yy) l, = ax2.plot(xx, yy, "k-") l.set_clip_path(ax1.patch)
def curvelinear_test2(fig): """ Polar projection, but in a rectangular box. """ # PolarAxes.PolarTransform takes radian. However, we want our coordinate # system in degree tr = Affine2D().scale(np.pi / 180, 1) + PolarAxes.PolarTransform() # Polar projection, which involves cycle, and also has limits in # its coordinates, needs a special method to find the extremes # (min, max of the coordinate within the view). extreme_finder = angle_helper.ExtremeFinderCycle( nx=20, ny=20, # Number of sampling points in each direction. lon_cycle=360, lat_cycle=None, lon_minmax=None, lat_minmax=(0, np.inf), ) # Find grid values appropriate for the coordinate (degree, minute, second). grid_locator1 = angle_helper.LocatorDMS(12) # Use an appropriate formatter. Note that the acceptable Locator and # Formatter classes are a bit different than that of Matplotlib, which # cannot directly be used here (this may be possible in the future). tick_formatter1 = angle_helper.FormatterDMS() grid_helper = GridHelperCurveLinear(tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, tick_formatter1=tick_formatter1) ax1 = fig.add_subplot(1, 2, 2, axes_class=HostAxes, grid_helper=grid_helper) # make ticklabels of right and top axis visible. ax1.axis["right"].major_ticklabels.set_visible(True) ax1.axis["top"].major_ticklabels.set_visible(True) # let right axis shows ticklabels for 1st coordinate (angle) ax1.axis["right"].get_helper().nth_coord_ticks = 0 # let bottom axis shows ticklabels for 2nd coordinate (radius) ax1.axis["bottom"].get_helper().nth_coord_ticks = 1 ax1.set_aspect(1) ax1.set_xlim(-5, 12) ax1.set_ylim(-5, 10) ax1.grid(True, zorder=0) # A parasite axes with given transform ax2 = ax1.get_aux_axes(tr) # note that ax2.transData == tr + ax1.transData # Anything you draw in ax2 will match the ticks and grids of ax1. ax1.parasites.append(ax2) ax2.plot(np.linspace(0, 30, 51), np.linspace(10, 10, 51), linewidth=2) ax2.pcolor(np.linspace(0, 90, 4), np.linspace(0, 10, 4), np.arange(9).reshape((3, 3))) ax2.contour(np.linspace(0, 90, 4), np.linspace(0, 10, 4), np.arange(16).reshape((4, 4)), colors="k")
def _get_affine_transform(self): return Affine2D() \ .scale(0.25) \ .translate(0.5, 0.5)
def curvelinear_test2(fig): """ polar projection, but in a rectangular box. """ # PolarAxes.PolarTransform takes radian. However, we want our coordinate # system in degree tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() # polar projection, which involves cycle, and also has limits in # its coordinates, needs a special method to find the extremes # (min, max of the coordinate within the view). # 20, 20 : number of sampling points along x, y direction extreme_finder = angle_helper.ExtremeFinderCycle( 20, 20, lon_cycle=360, lat_cycle=None, lon_minmax=None, lat_minmax=(0, np.inf), ) grid_locator1 = angle_helper.LocatorDMS(12) # Find a grid values appropriate for the coordinate (degree, # minute, second). tick_formatter1 = angle_helper.FormatterDMS() # And also uses an appropriate formatter. Note that,the # acceptable Locator and Formatter class is a bit different than # that of mpl's, and you cannot directly use mpl's Locator and # Formatter here (but may be possible in the future). grid_helper = GridHelperCurveLinear(tr, extreme_finder=extreme_finder, grid_locator1=grid_locator1, tick_formatter1=tick_formatter1) ax1 = SubplotHost(fig, 1, 2, 2, grid_helper=grid_helper) # make ticklabels of right and top axis visible. ax1.axis["right"].major_ticklabels.set_visible(True) ax1.axis["top"].major_ticklabels.set_visible(True) # let right axis shows ticklabels for 1st coordinate (angle) ax1.axis["right"].get_helper().nth_coord_ticks = 0 # let bottom axis shows ticklabels for 2nd coordinate (radius) ax1.axis["bottom"].get_helper().nth_coord_ticks = 1 fig.add_subplot(ax1) # A parasite axes with given transform ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal") # note that ax2.transData == tr + ax1.transData # Anthing you draw in ax2 will match the ticks and grids of ax1. ax1.parasites.append(ax2) intp = cbook.simple_linear_interpolation ax2.plot(intp(np.array([0, 30]), 50), intp(np.array([10., 10.]), 50), linewidth=2.0) ax1.set_aspect(1.) ax1.set_xlim(-5, 12) ax1.set_ylim(-5, 10) ax1.grid(True, zorder=0)
import matplotlib.pyplot as plt from matplotlib.transforms import Affine2D import mpl_toolkits.axisartist.floating_axes as floating_axes fig = plt.figure() plot_extents = 0, 10, 0, 10 transform = Affine2D().rotate_deg(45) helper = floating_axes.GridHelperCurveLinear(transform, plot_extents) ax = floating_axes.FloatingSubplot(fig, 111, grid_helper=helper) fig.add_subplot(ax) plt.show()
def __init__(self, parent_axes=None, parent_map=None, transform=None, coord_index=None, coord_type='scalar', coord_unit=None, coord_wrap=None, frame=None): # Keep a reference to the parent axes and the transform self.parent_axes = parent_axes self.parent_map = parent_map self.transform = transform self.coord_index = coord_index self.coord_unit = coord_unit self.frame = frame self.set_coord_type(coord_type, coord_wrap) # Initialize ticks self.dpi_transform = Affine2D() self.offset_transform = ScaledTranslation(0, 0, self.dpi_transform) self.ticks = Ticks(transform=parent_axes.transData + self.offset_transform) # Initialize tick labels self.ticklabels = TickLabels( self.frame, transform=None, # display coordinates figure=parent_axes.get_figure()) self.ticks.display_minor_ticks(False) self.minor_frequency = 5 # Initialize axis labels self.axislabels = AxisLabels( self.frame, transform=None, # display coordinates figure=parent_axes.get_figure()) # Initialize container for the grid lines self.grid_lines = [] # Initialize grid style. Take defaults from matplotlib.rcParams. # Based on matplotlib.axis.YTick._get_gridline. # # Matplotlib's gridlines use Line2D, but ours use PathPatch. # Patches take a slightly different format of linestyle argument. lines_to_patches_linestyle = { '-': 'solid', '--': 'dashed', '-.': 'dashdot', ':': 'dotted', 'none': 'none', 'None': 'none', ' ': 'none', '': 'none' } self.grid_lines_kwargs = { 'visible': False, 'facecolor': 'none', 'edgecolor': rcParams['grid.color'], 'linestyle': lines_to_patches_linestyle[rcParams['grid.linestyle']], 'linewidth': rcParams['grid.linewidth'], 'alpha': rcParams.get('grid.alpha', 1.0), 'transform': self.parent_axes.transData }
def test_cursor_data(): from matplotlib.backend_bases import MouseEvent fig, ax = plt.subplots() im = ax.imshow(np.arange(100).reshape(10, 10), origin='upper') x, y = 4, 4 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) == 44 # Now try for a point outside the image # Tests issue #4957 x, y = 10.1, 4 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) is None # Hmm, something is wrong here... I get 0, not None... # But, this works further down in the tests with extents flipped # x, y = 0.1, -0.1 # xdisp, ydisp = ax.transData.transform([x, y]) # event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) # z = im.get_cursor_data(event) # assert z is None, "Did not get None, got %d" % z ax.clear() # Now try with the extents flipped. im = ax.imshow(np.arange(100).reshape(10, 10), origin='lower') x, y = 4, 4 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) == 44 fig, ax = plt.subplots() im = ax.imshow(np.arange(100).reshape(10, 10), extent=[0, 0.5, 0, 0.5]) x, y = 0.25, 0.25 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) == 55 # Now try for a point outside the image # Tests issue #4957 x, y = 0.75, 0.25 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) is None x, y = 0.01, -0.01 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) is None # Now try with additional transform applied to the image artist trans = Affine2D().scale(2).rotate(0.5) im = ax.imshow(np.arange(100).reshape(10, 10), transform=trans + ax.transData) x, y = 3, 10 xdisp, ydisp = ax.transData.transform([x, y]) event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) assert im.get_cursor_data(event) == 44
def add(self, patchlabel='', flows=None, orientations=None, labels='', trunklength=1.0, pathlengths=0.25, prior=None, connect=(0, 0), rotation=0, **kwargs): """ Add a simple Sankey diagram with flows at the same hierarchical level. Return value is the instance of :class:`Sankey`. Optional keyword arguments: =============== =================================================== Keyword Description =============== =================================================== *patchlabel* label to be placed at the center of the diagram Note: *label* (not *patchlabel*) will be passed to the patch through ``**kwargs`` and can be used to create an entry in the legend. *flows* array of flow values By convention, inputs are positive and outputs are negative. *orientations* list of orientations of the paths Valid values are 1 (from/to the top), 0 (from/to the left or right), or -1 (from/to the bottom). If *orientations* == 0, inputs will break in from the left and outputs will break away to the right. *labels* list of specifications of the labels for the flows Each value may be *None* (no labels), '' (just label the quantities), or a labeling string. If a single value is provided, it will be applied to all flows. If an entry is a non-empty string, then the quantity for the corresponding flow will be shown below the string. However, if the *unit* of the main diagram is None, then quantities are never shown, regardless of the value of this argument. *trunklength* length between the bases of the input and output groups *pathlengths* list of lengths of the arrows before break-in or after break-away If a single value is given, then it will be applied to the first (inside) paths on the top and bottom, and the length of all other arrows will be justified accordingly. The *pathlengths* are not applied to the horizontal inputs and outputs. *prior* index of the prior diagram to which this diagram should be connected *connect* a (prior, this) tuple indexing the flow of the prior diagram and the flow of this diagram which should be connected If this is the first diagram or *prior* is *None*, *connect* will be ignored. *rotation* angle of rotation of the diagram [deg] *rotation* is ignored if this diagram is connected to an existing one (using *prior* and *connect*). The interpretation of the *orientations* argument will be rotated accordingly (e.g., if *rotation* == 90, an *orientations* entry of 1 means to/from the left). =============== =================================================== Valid kwargs are :meth:`matplotlib.patches.PathPatch` arguments: %(Patch)s As examples, ``fill=False`` and ``label='A legend entry'``. By default, ``facecolor='#bfd1d4'`` (light blue) and ``linewidth=0.5``. The indexing parameters (*prior* and *connect*) are zero-based. The flows are placed along the top of the diagram from the inside out in order of their index within the *flows* list or array. They are placed along the sides of the diagram from the top down and along the bottom from the outside in. If the sum of the inputs and outputs is nonzero, the discrepancy will appear as a cubic Bezier curve along the top and bottom edges of the trunk. .. seealso:: :meth:`finish` """ # Check and preprocess the arguments. if flows is None: flows = np.array([1.0, -1.0]) else: flows = np.array(flows) n = flows.shape[0] # Number of flows if rotation is None: rotation = 0 else: # In the code below, angles are expressed in deg/90. rotation /= 90.0 if orientations is None: orientations = [0, 0] if len(orientations) != n: raise ValueError( "orientations and flows must have the same length.\n" "orientations has length %d, but flows has length %d." % (len(orientations), n)) if labels != '' and getattr(labels, '__iter__', False): # iterable() isn't used because it would give True if labels is a # string if len(labels) != n: raise ValueError( "If labels is a list, then labels and flows must have the " "same length.\nlabels has length %d, but flows has length %d." % (len(labels), n)) else: labels = [labels] * n if trunklength < 0: raise ValueError( "trunklength is negative.\nThis isn't allowed, because it would " "cause poor layout.") if np.abs(np.sum(flows)) > self.tolerance: _log.info("The sum of the flows is nonzero (%f).\nIs the " "system not at steady state?", np.sum(flows)) scaled_flows = self.scale * flows gain = sum(max(flow, 0) for flow in scaled_flows) loss = sum(min(flow, 0) for flow in scaled_flows) if not (0.5 <= gain <= 2.0): _log.info( "The scaled sum of the inputs is %f.\nThis may " "cause poor layout.\nConsider changing the scale so" " that the scaled sum is approximately 1.0.", gain) if not (-2.0 <= loss <= -0.5): _log.info( "The scaled sum of the outputs is %f.\nThis may " "cause poor layout.\nConsider changing the scale so" " that the scaled sum is approximately 1.0.", gain) if prior is not None: if prior < 0: raise ValueError("The index of the prior diagram is negative.") if min(connect) < 0: raise ValueError( "At least one of the connection indices is negative.") if prior >= len(self.diagrams): raise ValueError( "The index of the prior diagram is %d, but there are " "only %d other diagrams.\nThe index is zero-based." % (prior, len(self.diagrams))) if connect[0] >= len(self.diagrams[prior].flows): raise ValueError( "The connection index to the source diagram is %d, but " "that diagram has only %d flows.\nThe index is zero-based." % (connect[0], len(self.diagrams[prior].flows))) if connect[1] >= n: raise ValueError( "The connection index to this diagram is %d, but this diagram" "has only %d flows.\n The index is zero-based." % (connect[1], n)) if self.diagrams[prior].angles[connect[0]] is None: raise ValueError( "The connection cannot be made. Check that the magnitude " "of flow %d of diagram %d is greater than or equal to the " "specified tolerance." % (connect[0], prior)) flow_error = (self.diagrams[prior].flows[connect[0]] + flows[connect[1]]) if abs(flow_error) >= self.tolerance: raise ValueError( "The scaled sum of the connected flows is %f, which is not " "within the tolerance (%f)." % (flow_error, self.tolerance)) # Determine if the flows are inputs. are_inputs = [None] * n for i, flow in enumerate(flows): if flow >= self.tolerance: are_inputs[i] = True elif flow <= -self.tolerance: are_inputs[i] = False else: _log.info( "The magnitude of flow %d (%f) is below the " "tolerance (%f).\nIt will not be shown, and it " "cannot be used in a connection." % (i, flow, self.tolerance)) # Determine the angles of the arrows (before rotation). angles = [None] * n for i, (orient, is_input) in enumerate(zip(orientations, are_inputs)): if orient == 1: if is_input: angles[i] = DOWN elif not is_input: # Be specific since is_input can be None. angles[i] = UP elif orient == 0: if is_input is not None: angles[i] = RIGHT else: if orient != -1: raise ValueError( "The value of orientations[%d] is %d, " "but it must be [ -1 | 0 | 1 ]." % (i, orient)) if is_input: angles[i] = UP elif not is_input: angles[i] = DOWN # Justify the lengths of the paths. if iterable(pathlengths): if len(pathlengths) != n: raise ValueError( "If pathlengths is a list, then pathlengths and flows must " "have the same length.\npathlengths has length %d, but flows " "has length %d." % (len(pathlengths), n)) else: # Make pathlengths into a list. urlength = pathlengths ullength = pathlengths lrlength = pathlengths lllength = pathlengths d = dict(RIGHT=pathlengths) pathlengths = [d.get(angle, 0) for angle in angles] # Determine the lengths of the top-side arrows # from the middle outwards. for i, (angle, is_input, flow) in enumerate(zip(angles, are_inputs, scaled_flows)): if angle == DOWN and is_input: pathlengths[i] = ullength ullength += flow elif angle == UP and not is_input: pathlengths[i] = urlength urlength -= flow # Flow is negative for outputs. # Determine the lengths of the bottom-side arrows # from the middle outwards. for i, (angle, is_input, flow) in enumerate(reversed(list(zip( angles, are_inputs, scaled_flows)))): if angle == UP and is_input: pathlengths[n - i - 1] = lllength lllength += flow elif angle == DOWN and not is_input: pathlengths[n - i - 1] = lrlength lrlength -= flow # Determine the lengths of the left-side arrows # from the bottom upwards. has_left_input = False for i, (angle, is_input, spec) in enumerate(reversed(list(zip( angles, are_inputs, zip(scaled_flows, pathlengths))))): if angle == RIGHT: if is_input: if has_left_input: pathlengths[n - i - 1] = 0 else: has_left_input = True # Determine the lengths of the right-side arrows # from the top downwards. has_right_output = False for i, (angle, is_input, spec) in enumerate(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))): if angle == RIGHT: if not is_input: if has_right_output: pathlengths[i] = 0 else: has_right_output = True # Begin the subpaths, and smooth the transition if the sum of the flows # is nonzero. urpath = [(Path.MOVETO, [(self.gap - trunklength / 2.0), # Upper right gain / 2.0]), (Path.LINETO, [(self.gap - trunklength / 2.0) / 2.0, gain / 2.0]), (Path.CURVE4, [(self.gap - trunklength / 2.0) / 8.0, gain / 2.0]), (Path.CURVE4, [(trunklength / 2.0 - self.gap) / 8.0, -loss / 2.0]), (Path.LINETO, [(trunklength / 2.0 - self.gap) / 2.0, -loss / 2.0]), (Path.LINETO, [(trunklength / 2.0 - self.gap), -loss / 2.0])] llpath = [(Path.LINETO, [(trunklength / 2.0 - self.gap), # Lower left loss / 2.0]), (Path.LINETO, [(trunklength / 2.0 - self.gap) / 2.0, loss / 2.0]), (Path.CURVE4, [(trunklength / 2.0 - self.gap) / 8.0, loss / 2.0]), (Path.CURVE4, [(self.gap - trunklength / 2.0) / 8.0, -gain / 2.0]), (Path.LINETO, [(self.gap - trunklength / 2.0) / 2.0, -gain / 2.0]), (Path.LINETO, [(self.gap - trunklength / 2.0), -gain / 2.0])] lrpath = [(Path.LINETO, [(trunklength / 2.0 - self.gap), # Lower right loss / 2.0])] ulpath = [(Path.LINETO, [self.gap - trunklength / 2.0, # Upper left gain / 2.0])] # Add the subpaths and assign the locations of the tips and labels. tips = np.zeros((n, 2)) label_locations = np.zeros((n, 2)) # Add the top-side inputs and outputs from the middle outwards. for i, (angle, is_input, spec) in enumerate(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))): if angle == DOWN and is_input: tips[i, :], label_locations[i, :] = self._add_input( ulpath, angle, *spec) elif angle == UP and not is_input: tips[i, :], label_locations[i, :] = self._add_output( urpath, angle, *spec) # Add the bottom-side inputs and outputs from the middle outwards. for i, (angle, is_input, spec) in enumerate(reversed(list(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))))): if angle == UP and is_input: tip, label_location = self._add_input(llpath, angle, *spec) tips[n - i - 1, :] = tip label_locations[n - i - 1, :] = label_location elif angle == DOWN and not is_input: tip, label_location = self._add_output(lrpath, angle, *spec) tips[n - i - 1, :] = tip label_locations[n - i - 1, :] = label_location # Add the left-side inputs from the bottom upwards. has_left_input = False for i, (angle, is_input, spec) in enumerate(reversed(list(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))))): if angle == RIGHT and is_input: if not has_left_input: # Make sure the lower path extends # at least as far as the upper one. if llpath[-1][1][0] > ulpath[-1][1][0]: llpath.append((Path.LINETO, [ulpath[-1][1][0], llpath[-1][1][1]])) has_left_input = True tip, label_location = self._add_input(llpath, angle, *spec) tips[n - i - 1, :] = tip label_locations[n - i - 1, :] = label_location # Add the right-side outputs from the top downwards. has_right_output = False for i, (angle, is_input, spec) in enumerate(zip( angles, are_inputs, list(zip(scaled_flows, pathlengths)))): if angle == RIGHT and not is_input: if not has_right_output: # Make sure the upper path extends # at least as far as the lower one. if urpath[-1][1][0] < lrpath[-1][1][0]: urpath.append((Path.LINETO, [lrpath[-1][1][0], urpath[-1][1][1]])) has_right_output = True tips[i, :], label_locations[i, :] = self._add_output( urpath, angle, *spec) # Trim any hanging vertices. if not has_left_input: ulpath.pop() llpath.pop() if not has_right_output: lrpath.pop() urpath.pop() # Concatenate the subpaths in the correct order (clockwise from top). path = (urpath + self._revert(lrpath) + llpath + self._revert(ulpath) + [(Path.CLOSEPOLY, urpath[0][1])]) # Create a patch with the Sankey outline. codes, vertices = zip(*path) vertices = np.array(vertices) def _get_angle(a, r): if a is None: return None else: return a + r if prior is None: if rotation != 0: # By default, none of this is needed. angles = [_get_angle(angle, rotation) for angle in angles] rotate = Affine2D().rotate_deg(rotation * 90).transform_affine tips = rotate(tips) label_locations = rotate(label_locations) vertices = rotate(vertices) text = self.ax.text(0, 0, s=patchlabel, ha='center', va='center') else: rotation = (self.diagrams[prior].angles[connect[0]] - angles[connect[1]]) angles = [_get_angle(angle, rotation) for angle in angles] rotate = Affine2D().rotate_deg(rotation * 90).transform_affine tips = rotate(tips) offset = self.diagrams[prior].tips[connect[0]] - tips[connect[1]] translate = Affine2D().translate(*offset).transform_affine tips = translate(tips) label_locations = translate(rotate(label_locations)) vertices = translate(rotate(vertices)) kwds = dict(s=patchlabel, ha='center', va='center') text = self.ax.text(*offset, **kwds) if rcParams['_internal.classic_mode']: fc = kwargs.pop('fc', kwargs.pop('facecolor', '#bfd1d4')) lw = kwargs.pop('lw', kwargs.pop('linewidth', 0.5)) else: fc = kwargs.pop('fc', kwargs.pop('facecolor', None)) lw = kwargs.pop('lw', kwargs.pop('linewidth', None)) if fc is None: fc = next(self.ax._get_patches_for_fill.prop_cycler)['color'] patch = PathPatch(Path(vertices, codes), fc=fc, lw=lw, **kwargs) self.ax.add_patch(patch) # Add the path labels. texts = [] for number, angle, label, location in zip(flows, angles, labels, label_locations): if label is None or angle is None: label = '' elif self.unit is not None: quantity = self.format % abs(number) + self.unit if label != '': label += "\n" label += quantity texts.append(self.ax.text(x=location[0], y=location[1], s=label, ha='center', va='center')) # Text objects are placed even they are empty (as long as the magnitude # of the corresponding flow is larger than the tolerance) in case the # user wants to provide labels later. # Expand the size of the diagram if necessary. self.extent = (min(np.min(vertices[:, 0]), np.min(label_locations[:, 0]), self.extent[0]), max(np.max(vertices[:, 0]), np.max(label_locations[:, 0]), self.extent[1]), min(np.min(vertices[:, 1]), np.min(label_locations[:, 1]), self.extent[2]), max(np.max(vertices[:, 1]), np.max(label_locations[:, 1]), self.extent[3])) # Include both vertices _and_ label locations in the extents; there are # where either could determine the margins (e.g., arrow shoulders). # Add this diagram as a subdiagram. self.diagrams.append(Bunch(patch=patch, flows=flows, angles=angles, tips=tips, text=text, texts=texts)) # Allow a daisy-chained call structure (see docstring for the class). return self
def get_axislabel_transform(self, axes): return Affine2D() #axes.transData
def build_cloud(wordweights, loose=False, seed=None, split_limit=2**-3, pad=1.10, visual_limit=2**-5, highest_weight=None ): """Convert a list of words and weights into a list of paths and weights. You should only use this function if you know what you're doing, or if you really don't want to cache the generated paths. Otherwise just use the WordCloud class. Args: wordweights: An iterator of the form [ (word, weight), (word, weight), ... ] such that the weights are in decreasing order. loose: If `true', words won't be broken up into rectangles after insertion. This results in a looser cloud, generated faster. seed: A random seed to use split_limit: When words are approximated by rectangles, the rectangles will have dimensions less than split_limit. Higher values result in a tighter cloud, at a cost of more CPU time. The largest word has height 1.0. pad: Expand a word's bounding box by a factor of `pad' before inserting it. This can actually result in a tighter cloud if you have many small words by leaving space between large words. visual_limit: Words with height smaller than visual_limit will be discarded. highest_weight: Experimental feature. If you provide an upper bound on the weights that will be seen you don't have to provide words and weights sorted. The resulting word cloud will be noticeably uglier. Generates: Tuples of the form (path, weight) such that: * No two paths intersect * Paths are fairly densely packed around the origin * All weights are normalized to fall in the interval [0, 1] """ if seed is not None: random.seed(seed) font_properties = font_manager.FontProperties( family="sans", weight="bold", stretch="condensed") xheight = TextPath((0,0), "x", prop=font_properties).get_extents().expanded(pad,pad).height # These are magic numbers. Most wordclouds will not exceed these bounds. # If they do, it will have to re-index all of the bounding boxes. index_bounds = (-16, -16, 16, 16) index = BboxQuadtree(index_bounds) if highest_weight is None: # Attempt to pull the first word and weight. If we fail, the wordweights # list is empty and we should just quit. # # All this nonsense is to ensure it accepts an iterator of words # correctly. iterwords = iter(wordweights) try: first_word, first_weight = iterwords.next() iterwords = chain([(first_word, first_weight)], iterwords) except StopIteration: return # We'll scale all of the weights down by this much. weight_scale = 1.0/first_weight else: weight_scale = 1.0/highest_weight iterwords = iter(wordweights) bboxes = list() bounds = transforms.Bbox(((-0.5, -0.5), (-0.5, -0.5))) for tword, tweight in iterwords: weight = tweight*weight_scale if weight < visual_limit: # You're not going to be able to see the word anyway. Quit # rendering words now. continue word_path = TextPath((0,0), tword, prop=font_properties) word_bbox = word_path.get_extents().expanded(pad, pad) # word_scale = weight/float(word_bbox.height) word_scale = weight/float(xheight) # When we build a TextPath at (0,0) it doesn't necessarily have # its corner at (0,0). So we have to translate to the origin, # scale down, then translate to center it. Feel free to simplify # this if you want. word_trans = Affine2D.identity().translate( -word_bbox.xmin, -word_bbox.ymin ).scale(word_scale).translate( -0.5*abs(word_bbox.width)*word_scale, -0.5*abs(word_bbox.height)*word_scale ) word_path = word_path.transformed(word_trans) word_bbox = word_path.get_extents().expanded(pad, pad) if weight > split_limit: # Big words we place carefully, trying to make the dimensions of # the cloud equal and center it around the origin. gaps = ( ("left", bounds.xmin), ("bottom", bounds.ymin), ("right", bounds.xmax), ("top", bounds.ymax) ) direction = min(gaps, key=lambda g: abs(g[1]))[0] else: # Small words we place randomly. direction = random.choice( [ "left", "bottom", "right", "top" ] ) # Randomly place the word along an edge... if direction in ( "top", "bottom" ): center = random_position(bounds.xmin, bounds.xmax) elif direction in ( "right", "left" ): center = random_position(bounds.ymin, bounds.ymax) # And push it toward an axis. if direction == "top": bbox = word_bbox.translated( center, index_bounds[3] ) xpos, ypos = push_bbox_down( bbox, bboxes, index ) elif direction == "right": bbox = word_bbox.translated( index_bounds[2], center ) xpos, ypos = push_bbox_left( bbox, bboxes, index ) elif direction == "bottom": bbox = word_bbox.translated( center, index_bounds[1] ) xpos, ypos = push_bbox_up( bbox, bboxes, index ) elif direction == "left": bbox = word_bbox.translated( index_bounds[0], center ) xpos, ypos = push_bbox_right( bbox, bboxes, index ) # Now alternate pushing the word toward different axes until either # it stops movign or we get sick of it. max_moves = 2 moves = 0 while moves < max_moves and (moves == 0 or prev_xpos != xpos or prev_ypos != ypos): moves += 1 prev_xpos = xpos prev_ypos = ypos if direction in ["top", "bottom", "vertical"]: if xpos > 0: bbox = word_bbox.translated( xpos, ypos ) xpos, ypos = push_bbox_left( bbox, bboxes, index ) elif xpos < 0: bbox = word_bbox.translated( xpos, ypos ) xpos, ypos = push_bbox_right( bbox, bboxes, index ) direction = "horizontal" elif direction in ["left", "right", "horizontal"]: if ypos > 0: bbox = word_bbox.translated( xpos, ypos ) xpos, ypos = push_bbox_down( bbox, bboxes, index ) elif ypos < 0: bbox = word_bbox.translated( xpos, ypos ) xpos, ypos = push_bbox_up( bbox, bboxes, index ) direction = "vertical" wordtrans = Affine2D.identity().translate( xpos, ypos ) transpath = word_path.transformed(wordtrans) bbox = transpath.get_extents() # Swallow the new word into the bounding box for the word cloud. bounds = matplotlib.transforms.Bbox.union( [ bounds, bbox ] ) # We need to check if we've expanded past the bounds of our quad tree. # If so we'll need to expand the bounds and then re-index. new_bounds = index_bounds while not BoxifyWord.bbox_covers( # FIXME: Why am I not just doing this with a couple of logarithms? matplotlib.transforms.Bbox(((new_bounds[0], new_bounds[1]), (new_bounds[2], new_bounds[3]))), bounds ): new_bounds = tuple( map( lambda x: 2*x, index_bounds ) ) if new_bounds != index_bounds: # We need to re-index. index_bounds = new_bounds index = BboxQuadtree(index_bounds) for i, b in enumerate(bboxes): index.add_bbox(i, b) # Approximate the new word by rectangles (unless it's too small) and # insert them into the index. if not loose and max(abs(bbox.width), abs(bbox.height)) > split_limit: for littlebox in BoxifyWord.splitword( bbox, transpath, limit=split_limit ): bboxes.append( littlebox ) index.add_bbox( len(bboxes)-1, littlebox ) else: bboxes.append( bbox ) index.add_bbox( len(bboxes)-1, bbox ) yield (transpath, weight)