def convective_deriv(a, b=None, bnd=True): r"""Compute (a \dot \nabla) b for vector fields a and b""" # [(B \dot \nabla) B]_j = B_i \partial_i B_j # FIXME: this is a lot of temporary arrays if bnd: if b is None: b = viscid.extend_boundaries(a, order=0, crd_order=0) else: b = viscid.extend_boundaries(b, order=0, crd_order=0) else: if b is None: b = a a = a['x=1:-1, y=1:-1, z=1:-1'] if b.nr_comps > 1: diBj = [[None, None, None], [None, None, None], [None, None, None]] for j, jcmp in enumerate('xyz'): g = grad(b[jcmp], bnd=False) for i, icmp in enumerate('xyz'): diBj[i][j] = g[icmp] dest = viscid.zeros(a.crds, nr_comps=3) for i, icmp in enumerate('xyz'): for j, jcmp in enumerate('xyz'): dest[jcmp][...] += a[icmp] * diBj[i][j] else: dest = dot(a, grad(b, bnd=False)) return dest
def grad(fld, bnd=True): """2nd order centeral diff, 1st order @ boundaries if bnd""" # vx, vy, vz = fld.component_views() if bnd: fld = viscid.extend_boundaries(fld, order=0, crd_order=0) if fld.iscentered("Cell"): crdx, crdy, crdz = fld.get_crds_cc(shaped=True) # divcenter = "Cell" # divcrds = coordinate.NonuniformCartesianCrds(fld.crds.get_clist(np.s_[1:-1])) # divcrds = fld.crds.slice_keep(np.s_[1:-1, 1:-1, 1:-1]) elif fld.iscentered("Node"): crdx, crdy, crdz = fld.get_crds_nc(shaped=True) # divcenter = "Node" # divcrds = coordinate.NonuniformCartesianCrds(fld.crds.get_clist(np.s_[1:-1])) # divcrds = fld.crds.slice_keep(np.s_[1:-1, 1:-1, 1:-1]) else: raise NotImplementedError("Can only do cell and node centered gradients") v = fld.data g = viscid.zeros(fld['x=1:-1, y=1:-1, z=1:-1'].crds, nr_comps=3) xp, xm = crdx[2:, :, :], crdx[:-2, : , : ] # pylint: disable=bad-whitespace yp, ym = crdy[ :, 2:, :], crdy[: , :-2, : ] # pylint: disable=bad-whitespace zp, zm = crdz[ :, :, 2:], crdz[: , : , :-2] # pylint: disable=bad-whitespace vxp, vxm = v[2: , 1:-1, 1:-1], v[ :-2, 1:-1, 1:-1] # pylint: disable=bad-whitespace vyp, vym = v[1:-1, 2: , 1:-1], v[1:-1, :-2, 1:-1] # pylint: disable=bad-whitespace vzp, vzm = v[1:-1, 1:-1, 2: ], v[1:-1, 1:-1, :-2] # pylint: disable=bad-whitespace g['x'].data[...] = ne.evaluate("(vxp-vxm)/(xp-xm)") g['y'].data[...] = ne.evaluate("(vyp-vym)/(yp-ym)") g['z'].data[...] = ne.evaluate("(vzp-vzm)/(zp-zm)") return g
def _main(): x = np.linspace(-1, 1, 128) y = z = np.linspace(-0.25, 0.25, 8) B = viscid.zeros((x, y, z), nr_comps=3, layout='interlaced', name="B") X, Y, Z = B.get_crds("xyz", shaped=True) # pylint: disable=unused-variable xl, yl, zl = B.xl # pylint: disable=unused-variable xh, yh, zh = B.xh # pylint: disable=unused-variable xm, ym, zm = 0.5 * (B.xl + B.xh) # pylint: disable=unused-variable B['x'] = 0.0 # np.sin(1.0 * np.pi * X / (xh - xl) + 0.5 * np.pi) B['y'] = np.sin(1.0 * np.pi * X / (xh - xl) + 0.5 * np.pi) B['z'] = np.sin(1.0 * np.pi * X / (xh - xl) - 1.0 * np.pi) B += 0.33 * np.random.random_sample(B.shape) # R = viscid.a2b_rotm((1, 0, 0), (1, 0, 1)) # B[...] = np.einsum("ij,lmnj->lmni", R, B) lmn = find_minvar_lmn(B, (xl, ym, zm), (xh, ym, zm), l_basis=None) # lmn = find_minvar_lmn(B, (xl, ym, zm), (xh, ym, zm), l_basis=(0, 0, 1)) print("LMN matrix:\n", lmn, sep='') ########## from viscid.plot import vpyplot as vlt from matplotlib import pyplot as plt p0 = np.array((xm, ym, zm)).reshape((3, )) pl = p0 + 0.25 * lmn[:, 0] pm = p0 + 0.25 * lmn[:, 1] pn = p0 + 0.25 * lmn[:, 2] print("p0", p0) print("pl", pl) print("pm", pm) print("pn", pn) vlt.subplot(211) vlt.plot2d_quiver(B['z=0j']) plt.plot([p0[0], pl[0]], [p0[1], pl[1]], color='r', ls='-') plt.plot([p0[0], pm[0]], [p0[1], pm[1]], color='c', ls='-') plt.plot([p0[0], pn[0]], [p0[1], pn[1]], color='b', ls='-') plt.ylabel("Y") vlt.subplot(212) vlt.plot2d_quiver(B['y=0j']) plt.plot([p0[0], pl[0]], [p0[2], pl[2]], color='r', ls='-') plt.plot([p0[0], pm[0]], [p0[2], pm[2]], color='c', ls='-') plt.plot([p0[0], pn[0]], [p0[2], pn[2]], color='b', ls='-') plt.xlabel("X") plt.ylabel("Z") vlt.show() ########## return 0
def _main(): x = np.linspace(-1, 1, 128) y = z = np.linspace(-0.25, 0.25, 8) B = viscid.zeros((x, y, z), nr_comps=3, layout='interlaced', name="B") X, Y, Z = B.get_crds("xyz", shaped=True) # pylint: disable=unused-variable xl, yl, zl = B.xl # pylint: disable=unused-variable xh, yh, zh = B.xh # pylint: disable=unused-variable xm, ym, zm = 0.5 * (B.xl + B.xh) # pylint: disable=unused-variable B['x'] = 0.0 # np.sin(1.0 * np.pi * X / (xh - xl) + 0.5 * np.pi) B['y'] = np.sin(1.0 * np.pi * X / (xh - xl) + 0.5 * np.pi) B['z'] = np.sin(1.0 * np.pi * X / (xh - xl) - 1.0 * np.pi) B += 0.33 * np.random.random_sample(B.shape) # R = viscid.a2b_rotm((1, 0, 0), (1, 0, 1)) # B[...] = np.einsum("ij,lmnj->lmni", R, B) lmn = find_minvar_lmn(B, (xl, ym, zm), (xh, ym, zm), l_basis=None) # lmn = find_minvar_lmn(B, (xl, ym, zm), (xh, ym, zm), l_basis=(0, 0, 1)) print("LMN matrix:\n", lmn, sep='') ########## from viscid.plot import vpyplot as vlt from matplotlib import pyplot as plt p0 = np.array((xm, ym, zm)).reshape((3,)) pl = p0 + 0.25 * lmn[:, 0] pm = p0 + 0.25 * lmn[:, 1] pn = p0 + 0.25 * lmn[:, 2] print("p0", p0) print("pl", pl) print("pm", pm) print("pn", pn) vlt.subplot(211) vlt.plot2d_quiver(B['z=0j']) plt.plot([p0[0], pl[0]], [p0[1], pl[1]], color='r', ls='-') plt.plot([p0[0], pm[0]], [p0[1], pm[1]], color='c', ls='-') plt.plot([p0[0], pn[0]], [p0[1], pn[1]], color='b', ls='-') plt.ylabel("Y") vlt.subplot(212) vlt.plot2d_quiver(B['y=0j']) plt.plot([p0[0], pl[0]], [p0[2], pl[2]], color='r', ls='-') plt.plot([p0[0], pm[0]], [p0[2], pm[2]], color='c', ls='-') plt.plot([p0[0], pn[0]], [p0[2], pn[2]], color='b', ls='-') plt.xlabel("X") plt.ylabel("Z") vlt.show() ########## return 0
def make_arcade(eps, xl=(-10.0, 0.0, -10.0), xh=(10.0, 20.0, 10.0), L=(5, 5, 5), N=(32, 32, 32), layout='interlaced'): xl, xh = np.asarray(xl), np.asarray(xh) x = np.linspace(xl[0], xh[0], N[0]) y = np.linspace(xl[1], xh[1], N[1]) z = np.linspace(xl[2], xh[2], N[2]) b = viscid.zeros([x, y, z], nr_comps=3, layout=layout) e = viscid.zeros_like(b) X, Y, Z = b.get_crds('xyz', shaped=True) Y2 = Y**2 / L[1]**2 Z2 = Z**2 / L[2]**2 b['x'] = -1 - (eps * ((1 - Y2) / (1 + Y2)) * (1 / (1 + Z2))) b['y'] = X b['z'] = 0.2 e['z'] = Y / ((1 + Y2) * (1 + Z2)) return b, e
def apply_labels(labels=None, colors=None, ax=None, magnet=(0.5, 0.75), magnetcoords="axes fraction", padding=None, paddingcoords="offset points", choices="00:02:20:22", n_candidates=32, ignore_filling=False, spacing='linear', _debug=False, **kwargs): """Apply labels directly to series in liu of a legend The `choices` offsets are as follows:: --------------------- | 02 | 12 | 22 | |-------------------| | 01 | XX | 21 | |-------------------| | 00 | 10 | 20 | --------------------- Args: labels (sequence): Optional sequence of labels to override the labels already in the data series colors (str, sequence): color as hex string, list of hex strings to color each label, or an Nx4 ndarray of rgba values for N labels ax (matplotlib.axis): axis; defaults to `plt.gca()` magnet (tuple): prefer positions that are closer to the magnet magnetcoords (str): 'offset pixels', 'offset points' or 'axes fraction' padding (tuple): padding for text in the (x, y) directions paddingcoords (str): 'offset pixels', 'offset points' or 'axes fraction' choices (str): colon separated list of possible label positions relative to the data values. The positions are summarized above. alpha (float): alpha channel (opacity) of label text. Defaults to 1.0 to make text visible. Set to `None` to use the underlying alpha from the handle's color. n_candidates (int): number of potential label locations to consider for each data series. ignore_filling (bool): if True, then assume it's ok to place labels inside paths that are filled with color spacing (str): one of 'linear' or 'random' to specify how far apart candidate locations are spaced along path segments _debug (bool): Mark up all possible label locations **kwargs: passed to plt.annotate Returns: List: annotation objects """ if not ax: ax = plt.gca() if isinstance(colors, (list, tuple)): pass spacing = spacing.strip().lower() if spacing not in ('linear', 'random'): raise ValueError("Spacing '{0}' not understood".format(spacing)) rand_state = np.random.get_state() if spacing == 'random' else None if rand_state is not None: # save the RNG state to restore it later so that plotting functions # don't change the results of scripts that use random numbers np.random.seed(1) _xl, _xh = ax.get_xlim() _yl, _yh = ax.get_ylim() axbb0 = np.array([_xl, _yl]).reshape(1, 2) axbb1 = np.array([_xh, _yh]).reshape(1, 2) # choices:: "01:02:22" -> [(0, 1), (0, 2), (2, 2)] choices = [(int(c[0]), int(c[1])) for c in choices.split(':')] _size = kwargs.get('fontsize', kwargs.get('size', None)) _fontproperties = kwargs.get('fontproperties', None) font_size_pts = text_size_points(size=_size, fontproperties=_fontproperties) # set the default padding equal to the font size if paddingcoords == 'offset pixels': default_padding = font_size_pts * 72 / ax.figure.dpi elif paddingcoords == 'offset points': default_padding = font_size_pts elif paddingcoords == 'axes fraction': default_padding = 0.05 else: raise ValueError("Bad padding coords '{0}'".format(paddingcoords)) # print("fontsize pt:", font_size_pts, # "fontsize px:", xy_as_pixels([font_size_pts, font_size_pts], # 'offset points')[0]) if not isinstance(padding, (list, tuple)): padding = [padding, padding] padding = [default_padding if pd is None else pd for pd in padding] # print("padding::", paddingcoords, padding) magnet_px = xy_as_pixels(magnet, magnetcoords, ax=ax) padding_px = xy_as_pixels(padding, paddingcoords, ax=ax) # print("padding px::", padding_px) annotations = [] cand_map = {} for choice in choices: cand_map[choice] = np.zeros([n_candidates, 2, 2], dtype='f') # these paths are all the paths we can get our hands on so that the text # doesn't overlap them. bboxes around labels are added as we go paths_px = [] # here is a list of bounding boxes around the text boxes as we add them bbox_paths_px = [] is_filled = [] ## how many vertices to avoid ? # artist # collection # image # line # patch # table # container for line in ax.lines: paths_px += [ax.transData.transform_path(line.get_path())] is_filled += [False] for collection in ax.collections: for pth in collection.get_paths(): paths_px += [ax.transData.transform_path(pth)] is_filled += [collection.get_fill()] if ignore_filling: is_filled = [False] * len(is_filled) hands, hand_labels = ax.get_legend_handles_labels() colors = _cycle_colors(colors, len(hands)) # >>> debug >>> if _debug: import viscid from matplotlib import patches as mpatches from viscid.plot import vpyplot as vlt _fig_width = int(ax.figure.bbox.width) _fig_height = int(ax.figure.bbox.height) fig_fld = viscid.zeros((_fig_width, _fig_height), dtype='f', center='node') _X, _Y = fig_fld.get_crds(shaped=True) _axXL, _axYL, _axXH, _axYH = ax.bbox.extents _mask = np.bitwise_and(np.bitwise_and(_X >= _axXL, _X <= _axXH), np.bitwise_and(_Y >= _axYL, _Y <= _axYH)) fig_fld.data[_mask] = 1.0 dfig, dax = plt.subplots(1, 1, figsize=ax.figure.get_size_inches()) vlt.plot(fig_fld, ax=dax, cmap='ocean', colorbar=None) for _, path in enumerate(paths_px): dax.plot(path.vertices[:, 0], path.vertices[:, 1]) dfig.subplots_adjust(bottom=0.0, left=0.0, top=1.0, right=1.0) else: dfig, dax = None, None # <<< debug <<< for i, hand, label_i in zip(count(), hands, hand_labels): if labels and i < len(labels): label = labels[i] else: label = label_i # divine color of label if colors[i]: color = colors[i] else: try: color = hand.get_color() except AttributeError: color = hand.get_facecolor()[0] # get path vertices to determine candidate label positions try: verts = hand.get_path().vertices except AttributeError: verts = [p.vertices for p in hand.get_paths()] verts = np.concatenate(verts, axis=0) segl_dat = verts[:-1, :] segh_dat = verts[1:, :] # take out path segments that have one vertex outside the view _seg_mask = np.all( np.bitwise_and(segl_dat >= axbb0, segl_dat <= axbb1) & np.bitwise_and(segh_dat >= axbb0, segh_dat <= axbb1), axis=1) segl_dat = segl_dat[_seg_mask, :] segh_dat = segh_dat[_seg_mask, :] if np.prod(segl_dat.shape) == 0: print("no full segments are visible, skipping path", i, hand) continue segl_px = ax.transData.transform(segl_dat) segh_px = ax.transData.transform(segh_dat) seglen_px = np.linalg.norm(segh_px - segl_px, axis=1) # take out path segments that are 0 pixels in length _non0_seg_mask = seglen_px > 0 segl_dat = segl_dat[_non0_seg_mask, :] segh_dat = segh_dat[_non0_seg_mask, :] segl_px = segl_px[_non0_seg_mask, :] segh_px = segh_px[_non0_seg_mask, :] seglen_px = seglen_px[_non0_seg_mask] if np.prod(segl_dat.shape) == 0: print("no non-0 segments are visible, skipping path", i, hand) continue # i deeply appologize for how convoluted this got, but the punchline # is that each line segment gets candidates proportinal to their # length in pixels on the figure s_src = np.concatenate([[0], np.cumsum(seglen_px)]) if rand_state is not None: s_dest = s_src[-1] * np.sort(np.random.rand(n_candidates)) else: s_dest = np.linspace(0, s_src[-1], n_candidates) _diff = s_dest.reshape(1, -1) - s_src.reshape(-1, 1) iseg = np.argmin(np.ma.masked_where(_diff <= 0, _diff), axis=0) frac = (s_dest - s_src[iseg]) / seglen_px[iseg] root_dat = (segl_dat[iseg] + frac.reshape(-1, 1) * (segh_dat[iseg] - segl_dat[iseg])) root_px = ax.transData.transform(root_dat) # estimate the width and height of the label's text txt_size = np.array( estimate_text_size_px(label, fig=ax.figure, size=font_size_pts)) txt_size = txt_size.reshape([1, 2]) # this initial offset is needed to shift the center of the label # to the data point offset0 = -txt_size / 2 # now we can shift the label away from the data point by an amount # equal to half the text width/height + the padding offset1 = padding_px + txt_size / 2 for key, abs_px_arr in cand_map.items(): ioff = np.array(key, dtype='i').reshape(1, 2) - 1 total_offset = offset0 + ioff * offset1 # approx lower left corner of the text box in absolute pixels abs_px_arr[:, :, 0] = root_px + total_offset # approx upper right corner of the text box in absolute pixels abs_px_arr[:, :, 1] = abs_px_arr[:, :, 0] + txt_size # candidates_abs_px[i] has root @ root_px[i % n_candidates] candidates_abs_px = np.concatenate([cand_map[c] for c in choices], axis=0) # find how many other things each candidate overlaps n_overlaps = np.zeros_like(candidates_abs_px[:, 0, 0]) for k, candidate in enumerate(candidates_abs_px): cand_bbox = Bbox(candidate.T) # penalty for each time a box overlaps a path that's already # on the plot for ipth, path in enumerate(paths_px): if path.intersects_bbox(cand_bbox, filled=is_filled[ipth]): n_overlaps[k] += 1 # slightly larger penalty if we intersect a text box that we # just added to the plot for ipth, path in enumerate(bbox_paths_px): if path.intersects_bbox(cand_bbox, filled=is_filled[ipth]): n_overlaps[k] += 5 # big penalty if the candidate is out of the current view if not (ax.bbox.contains(*cand_bbox.min) and ax.bbox.contains(*cand_bbox.max)): n_overlaps[k] += 100 # sort candidates by distance between center of text box and magnet magnet_dist = np.linalg.norm(np.mean(candidates_abs_px, axis=-1) - magnet_px, axis=1) isorted = np.argsort(magnet_dist) magnet_dist = np.array(magnet_dist[isorted]) candidates_abs_px = np.array(candidates_abs_px[isorted, :, :]) n_overlaps = np.array(n_overlaps[isorted]) root_dat = np.array(root_dat[isorted % n_candidates, :]) root_px = np.array(root_px[isorted % n_candidates, :]) # sort candidates so the ones with the fewest overlaps are first # but do it with a stable algorithm so among the best candidates, # choose the one closest to the magnet sargs = np.argsort(n_overlaps, kind='mergesort') # >>> debug >>> if dax is not None: for _candidate, n_overlap in zip(candidates_abs_px, n_overlaps): _cand_bbox = Bbox(_candidate.T) _x0 = _cand_bbox.get_points()[0] _bbox_center = np.mean(_candidate, axis=-1) _ray_x = [_bbox_center[0], magnet_px[0]] _ray_y = [_bbox_center[1], magnet_px[1]] dax.plot(_ray_x, _ray_y, '-', alpha=0.3, color='grey') _rect = mpatches.Rectangle(_x0, _cand_bbox.width, _cand_bbox.height, fill=False) dax.add_patch(_rect) plt.text(_x0[0], _x0[1], label, color='gray') plt.text(_x0[0], _x0[1], '{0}'.format(n_overlap)) # <<< debug <<< # pick winning candidate and add its bounding box to this list of # paths to avoid winner_abs_px = candidates_abs_px[sargs[0], :, :] xy_root_px = root_px[sargs[0], :] xy_root_dat = np.array(root_dat[sargs[0], :]) xy_txt_offset = np.array(winner_abs_px[:, 0] - xy_root_px) corners = Bbox(winner_abs_px.T).corners()[(0, 1, 3, 2), :] bbox_paths_px += [Path(corners)] # a = plt.annotate(label, xy=xy_root_dat, xycoords='data', # xytext=xy_txt_offset, textcoords="offset pixels", # color=color, **kwargs) a = ax.annotate(label, xy=xy_root_dat, xycoords='data', xytext=xy_txt_offset, textcoords="offset pixels", color=color, **kwargs) annotations.append(a) if rand_state is not None: np.random.set_state(rand_state) return annotations
def _main(): f = viscid.load_file('~/dev/work/xi_fte_001/*.3d.*.xdmf') time_slice = ':' times = np.array([grid.time for grid in f.iter_times(time_slice)]) # XYZ coordinates of virtual satelites in warped "plasma sheet coords" x_sat_psc = np.linspace(-30, 0, 31) # X (GSE == PSC) y_sat_psc = np.linspace(-10, 10, 21) # Y (GSE == PSC) z_sat_psc = np.linspace(-2, 2, 5) # Z in PSC (z=0 is the plasma sheet) # the GSE z location of the virtual satelites in the warped plasma sheet # coordinates, so sat_z_gse_ts['x=5j, y=1j, z=0j'] would give the # plasma sheet location at x=5.0, y=1.0 # These fields depend on time because the plasma sheet moves in time sat_z_gse_ts = viscid.zeros([times, x_sat_psc, y_sat_psc, z_sat_psc], crd_names='txyz', center='node', name='PlasmaSheetZ_GSE') vx_ts = viscid.zeros_like(sat_z_gse_ts) bz_ts = viscid.zeros_like(sat_z_gse_ts) for itime, grid in enumerate(f.iter_times(time_slice)): print("Processing time slice", itime, grid.time) gse_slice = 'x=-35j:0j, y=-15j:15j, z=-6j:6j' bx = grid['bx'][gse_slice] bx_argmin = np.argmin(bx**2, axis=2) z_gse = bx.get_crd('z') # ps_zloc_gse is the plasma sheet z location along the GGCM grid x/y ps_z_gse = viscid.zeros_like(bx[:, :, 0:1]) ps_z_gse[...] = z_gse[bx_argmin] # Note: Here you could apply a gaussian filter to # ps_z_gse[:, :, 0].data in order to smooth the surface # if desired. Scipy / Scikit-Image have some functions # that do this # ok, we found the plasma sheet z GSE location on the actual GGCM # grid, but we just want a subset of that grid for our virtual # satelites, so just interpolate the ps z location to our subset ps_z_gse_subset = viscid.interp_trilin(ps_z_gse, sat_z_gse_ts[itime, :, :, 0:1], wrap=True) # now we know the plasma sheet z location in GSE, and how far # apart we want the satelites in z, so put those two things together # to get a bunch of satelite locations sat_z_gse_ts[itime] = ps_z_gse_subset.data + z_sat_psc.reshape(1, 1, -1) # make a seed generator that we can use to fill the vx and bz # time series for this instant in time sat_loc_gse = sat_z_gse_ts[itime].get_points() sat_loc_gse[2, :] = sat_z_gse_ts[itime].data.reshape(-1) # slicing the field before doing the interpolation makes this # faster for hdf5 data, but probably for other data too vx_ts[itime] = viscid.interp_trilin(grid['vx'][gse_slice], sat_loc_gse, wrap=False ).reshape(vx_ts.shape[1:]) bz_ts[itime] = viscid.interp_trilin(grid['bz'][gse_slice], sat_loc_gse, wrap=False ).reshape(bz_ts.shape[1:]) # 2d plots of the plasma sheet z location to make sure we did the # interpolation correctly if False: # pylint: disable=using-constant-test from viscid.plot import vpyplot as vlt fig, (ax0, ax1) = vlt.subplots(2, 1) # pylint: disable=unused-variable vlt.plot(ps_z_gse, ax=ax0, clim=(-5, 5)) vlt.plot(ps_z_gse_subset, ax=ax1, clim=(-5, 5)) vlt.auto_adjust_subplots() vlt.show() # make a 3d plot of the plasma sheet surface to verify that it # makes sense if True: # pylint: disable=using-constant-test from viscid.plot import vlab fig = vlab.figure(size=(1280, 800), bgcolor=(1, 1, 1), fgcolor=(0, 0, 0)) vlab.clf() # plot the plasma sheet coloured by vx # Note: points closer to x = 0 are unsightly since the plasma # sheet criteria starts to fall apart on the flanks, so # just remove the first few rows ps_z_gse_tail = ps_z_gse['x=:-2.25j'] ps_mesh_shape = [3, ps_z_gse_tail.shape[0], ps_z_gse_tail.shape[1]] ps_pts = ps_z_gse_tail.get_points().reshape(ps_mesh_shape) ps_pts[2, :, :] = ps_z_gse_tail[:, :, 0] plasma_sheet = viscid.RectilinearMeshPoints(ps_pts) ps_vx = viscid.interp_trilin(grid['vx'][gse_slice], plasma_sheet) _ = vlab.mesh_from_seeds(plasma_sheet, scalars=ps_vx) vx_clim = (-1400, 1400) vx_cmap = 'viridis' vlab.colorbar(title='Vx', clim=vx_clim, cmap=vx_cmap, nb_labels=5) # plot satelite locations as dots colored by Vx with the same # limits and color as the plasma sheet mesh sat3d = vlab.points3d(sat_loc_gse[0], sat_loc_gse[1], sat_loc_gse[2], vx_ts[itime].data.reshape(-1), scale_mode='none', scale_factor=0.2) vlab.apply_cmap(sat3d, clim=vx_clim, cmap=vx_cmap) # plot Earth for reference cotr = viscid.Cotr(dip_tilt=0.0) # pylint: disable=not-callable vlab.plot_blue_marble(r=1.0, lines=False, ntheta=64, nphi=128, rotate=cotr, crd_system='mhd') vlab.plot_earth_3d(radius=1.01, night_only=True, opacity=0.5, crd_system='gse') vlab.view(azimuth=45, elevation=70, distance=35.0, focalpoint=[-9, 3, -1]) vlab.savefig('plasma_sheet_3d_{0:02d}.png'.format(itime)) vlab.show() try: vlab.mlab.close(fig) except TypeError: pass # this happens if the figure is already closed # now do what we will with the time series... this is not a good # presentation of this data, but you get the idea from viscid.plot import vpyplot as vlt fig, axes = vlt.subplots(4, 4, figsize=(12, 12)) for ax_row, yloc in zip(axes, np.linspace(-5, 5, len(axes))[::-1]): for ax, xloc in zip(ax_row, np.linspace(4, 7, len(ax_row))): vlt.plot(vx_ts['x={0}j, y={1}j, z=0j'.format(xloc, yloc)], ax=ax) ax.set_ylabel('') vlt.plt.title('x = {0:g}, y = {1:g}'.format(xloc, yloc)) vlt.plt.suptitle('Vx [km/s]') vlt.auto_adjust_subplots() vlt.show() return 0
def apply_labels(labels=None, colors=None, ax=None, magnet=(0.5, 0.75), magnetcoords="axes fraction", padding=None, paddingcoords="offset points", choices="00:02:20:22", n_candidates=32, ignore_filling=False, spacing='linear', _debug=False, **kwargs): """Apply labels directly to series in liu of a legend The `choices` offsets are as follows:: --------------------- | 02 | 12 | 22 | |-------------------| | 01 | XX | 21 | |-------------------| | 00 | 10 | 20 | --------------------- Args: labels (sequence): Optional sequence of labels to override the labels already in the data series colors (str, sequence): color as hex string, list of hex strings to color each label, or an Nx4 ndarray of rgba values for N labels ax (matplotlib.axis): axis; defaults to `plt.gca()` magnet (tuple): prefer positions that are closer to the magnet magnetcoords (str): 'offset pixels', 'offset points' or 'axes fraction' padding (tuple): padding for text in the (x, y) directions paddingcoords (str): 'offset pixels', 'offset points' or 'axes fraction' choices (str): colon separated list of possible label positions relative to the data values. The positions are summarized above. alpha (float): alpha channel (opacity) of label text. Defaults to 1.0 to make text visible. Set to `None` to use the underlying alpha from the handle's color. n_candidates (int): number of potential label locations to consider for each data series. ignore_filling (bool): if True, then assume it's ok to place labels inside paths that are filled with color spacing (str): one of 'linear' or 'random' to specify how far apart candidate locations are spaced along path segments _debug (bool): Mark up all possible label locations **kwargs: passed to plt.annotate Returns: List: annotation objects """ if not ax: ax = plt.gca() if isinstance(colors, (list, tuple)): pass spacing = spacing.strip().lower() if spacing not in ('linear', 'random'): raise ValueError("Spacing '{0}' not understood".format(spacing)) rand_state = np.random.get_state() if spacing == 'random' else None if rand_state is not None: # save the RNG state to restore it later so that plotting functions # don't change the results of scripts that use random numbers np.random.seed(1) _xl, _xh = ax.get_xlim() _yl, _yh = ax.get_ylim() axbb0 = np.array([_xl, _yl]).reshape(1, 2) axbb1 = np.array([_xh, _yh]).reshape(1, 2) # choices:: "01:02:22" -> [(0, 1), (0, 2), (2, 2)] choices = [(int(c[0]), int(c[1])) for c in choices.split(':')] _size = kwargs.get('fontsize', kwargs.get('size', None)) _fontproperties = kwargs.get('fontproperties', None) font_size_pts = text_size_points(size=_size, fontproperties=_fontproperties) # set the default padding equal to the font size if paddingcoords == 'offset pixels': default_padding = font_size_pts * 72 / ax.figure.dpi elif paddingcoords == 'offset points': default_padding = font_size_pts elif paddingcoords == 'axes fraction': default_padding = 0.05 else: raise ValueError("Bad padding coords '{0}'".format(paddingcoords)) # print("fontsize pt:", font_size_pts, # "fontsize px:", xy_as_pixels([font_size_pts, font_size_pts], # 'offset points')[0]) if not isinstance(padding, (list, tuple)): padding = [padding, padding] padding = [default_padding if pd is None else pd for pd in padding] # print("padding::", paddingcoords, padding) magnet_px = xy_as_pixels(magnet, magnetcoords, ax=ax) padding_px = xy_as_pixels(padding, paddingcoords, ax=ax) # print("padding px::", padding_px) annotations = [] cand_map = {} for choice in choices: cand_map[choice] = np.zeros([n_candidates, 2, 2], dtype='f') # these paths are all the paths we can get our hands on so that the text # doesn't overlap them. bboxes around labels are added as we go paths_px = [] # here is a list of bounding boxes around the text boxes as we add them bbox_paths_px = [] is_filled = [] ## how many vertices to avoid ? # artist # collection # image # line # patch # table # container for line in ax.lines: paths_px += [ax.transData.transform_path(line.get_path())] is_filled += [False] for collection in ax.collections: for pth in collection.get_paths(): paths_px += [ax.transData.transform_path(pth)] is_filled += [collection.get_fill()] if ignore_filling: is_filled = [False] * len(is_filled) hands, hand_labels = ax.get_legend_handles_labels() colors = _cycle_colors(colors, len(hands)) # >>> debug >>> if _debug: import viscid from matplotlib import patches as mpatches from viscid.plot import vpyplot as vlt _fig_width = int(ax.figure.bbox.width) _fig_height = int(ax.figure.bbox.height) fig_fld = viscid.zeros((_fig_width, _fig_height), dtype='f', center='node') _X, _Y = fig_fld.get_crds(shaped=True) _axXL, _axYL, _axXH, _axYH = ax.bbox.extents _mask = np.bitwise_and(np.bitwise_and(_X >= _axXL, _X <= _axXH), np.bitwise_and(_Y >= _axYL, _Y <= _axYH)) fig_fld.data[_mask] = 1.0 dfig, dax = plt.subplots(1, 1, figsize=ax.figure.get_size_inches()) vlt.plot(fig_fld, ax=dax, cmap='ocean', colorbar=None) for _, path in enumerate(paths_px): dax.plot(path.vertices[:, 0], path.vertices[:, 1]) dfig.subplots_adjust(bottom=0.0, left=0.0, top=1.0, right=1.0) else: dfig, dax = None, None # <<< debug <<< for i, hand, label_i in zip(count(), hands, hand_labels): if labels and i < len(labels): label = labels[i] else: label = label_i # divine color of label if colors[i]: color = colors[i] else: try: color = hand.get_color() except AttributeError: color = hand.get_facecolor()[0] # get path vertices to determine candidate label positions try: verts = hand.get_path().vertices except AttributeError: verts = [p.vertices for p in hand.get_paths()] verts = np.concatenate(verts, axis=0) segl_dat = verts[:-1, :] segh_dat = verts[1:, :] # take out path segments that have one vertex outside the view _seg_mask = np.all(np.bitwise_and(segl_dat >= axbb0, segl_dat <= axbb1) & np.bitwise_and(segh_dat >= axbb0, segh_dat <= axbb1), axis=1) segl_dat = segl_dat[_seg_mask, :] segh_dat = segh_dat[_seg_mask, :] if np.prod(segl_dat.shape) == 0: print("no full segments are visible, skipping path", i, hand) continue segl_px = ax.transData.transform(segl_dat) segh_px = ax.transData.transform(segh_dat) seglen_px = np.linalg.norm(segh_px - segl_px, axis=1) # take out path segments that are 0 pixels in length _non0_seg_mask = seglen_px > 0 segl_dat = segl_dat[_non0_seg_mask, :] segh_dat = segh_dat[_non0_seg_mask, :] segl_px = segl_px[_non0_seg_mask, :] segh_px = segh_px[_non0_seg_mask, :] seglen_px = seglen_px[_non0_seg_mask] if np.prod(segl_dat.shape) == 0: print("no non-0 segments are visible, skipping path", i, hand) continue # i deeply appologize for how convoluted this got, but the punchline # is that each line segment gets candidates proportinal to their # length in pixels on the figure s_src = np.concatenate([[0], np.cumsum(seglen_px)]) if rand_state is not None: s_dest = s_src[-1] * np.sort(np.random.rand(n_candidates)) else: s_dest = np.linspace(0, s_src[-1], n_candidates) _diff = s_dest.reshape(1, -1) - s_src.reshape(-1, 1) iseg = np.argmin(np.ma.masked_where(_diff <= 0, _diff), axis=0) frac = (s_dest - s_src[iseg]) / seglen_px[iseg] root_dat = (segl_dat[iseg] + frac.reshape(-1, 1) * (segh_dat[iseg] - segl_dat[iseg])) root_px = ax.transData.transform(root_dat) # estimate the width and height of the label's text txt_size = np.array(estimate_text_size_px(label, fig=ax.figure, size=font_size_pts)) txt_size = txt_size.reshape([1, 2]) # this initial offset is needed to shift the center of the label # to the data point offset0 = -txt_size / 2 # now we can shift the label away from the data point by an amount # equal to half the text width/height + the padding offset1 = padding_px + txt_size / 2 for key, abs_px_arr in cand_map.items(): ioff = np.array(key, dtype='i').reshape(1, 2) - 1 total_offset = offset0 + ioff * offset1 # approx lower left corner of the text box in absolute pixels abs_px_arr[:, :, 0] = root_px + total_offset # approx upper right corner of the text box in absolute pixels abs_px_arr[:, :, 1] = abs_px_arr[:, :, 0] + txt_size # candidates_abs_px[i] has root @ root_px[i % n_candidates] candidates_abs_px = np.concatenate([cand_map[c] for c in choices], axis=0) # find how many other things each candidate overlaps n_overlaps = np.zeros_like(candidates_abs_px[:, 0, 0]) for k, candidate in enumerate(candidates_abs_px): cand_bbox = Bbox(candidate.T) # penalty for each time a box overlaps a path that's already # on the plot for ipth, path in enumerate(paths_px): if path.intersects_bbox(cand_bbox, filled=is_filled[ipth]): n_overlaps[k] += 1 # slightly larger penalty if we intersect a text box that we # just added to the plot for ipth, path in enumerate(bbox_paths_px): if path.intersects_bbox(cand_bbox, filled=is_filled[ipth]): n_overlaps[k] += 5 # big penalty if the candidate is out of the current view if not (ax.bbox.contains(*cand_bbox.min) and ax.bbox.contains(*cand_bbox.max)): n_overlaps[k] += 100 # sort candidates by distance between center of text box and magnet magnet_dist = np.linalg.norm(np.mean(candidates_abs_px, axis=-1) - magnet_px, axis=1) isorted = np.argsort(magnet_dist) magnet_dist = np.array(magnet_dist[isorted]) candidates_abs_px = np.array(candidates_abs_px[isorted, :, :]) n_overlaps = np.array(n_overlaps[isorted]) root_dat = np.array(root_dat[isorted % n_candidates, :]) root_px = np.array(root_px[isorted % n_candidates, :]) # sort candidates so the ones with the fewest overlaps are first # but do it with a stable algorithm so among the best candidates, # choose the one closest to the magnet sargs = np.argsort(n_overlaps, kind='mergesort') # >>> debug >>> if dax is not None: for _candidate, n_overlap in zip(candidates_abs_px, n_overlaps): _cand_bbox = Bbox(_candidate.T) _x0 = _cand_bbox.get_points()[0] _bbox_center = np.mean(_candidate, axis=-1) _ray_x = [_bbox_center[0], magnet_px[0]] _ray_y = [_bbox_center[1], magnet_px[1]] dax.plot(_ray_x, _ray_y, '-', alpha=0.3, color='grey') _rect = mpatches.Rectangle(_x0, _cand_bbox.width, _cand_bbox.height, fill=False) dax.add_patch(_rect) plt.text(_x0[0], _x0[1], label, color='gray') plt.text(_x0[0], _x0[1], '{0}'.format(n_overlap)) # <<< debug <<< # pick winning candidate and add its bounding box to this list of # paths to avoid winner_abs_px = candidates_abs_px[sargs[0], :, :] xy_root_px = root_px[sargs[0], :] xy_root_dat = np.array(root_dat[sargs[0], :]) xy_txt_offset = np.array(winner_abs_px[:, 0] - xy_root_px) corners = Bbox(winner_abs_px.T).corners()[(0, 1, 3, 2), :] bbox_paths_px += [Path(corners)] # a = plt.annotate(label, xy=xy_root_dat, xycoords='data', # xytext=xy_txt_offset, textcoords="offset pixels", # color=color, **kwargs) a = ax.annotate(label, xy=xy_root_dat, xycoords='data', xytext=xy_txt_offset, textcoords="offset pixels", color=color, **kwargs) annotations.append(a) if rand_state is not None: np.random.set_state(rand_state) return annotations
def _main(): parser = argparse.ArgumentParser(description=__doc__) args = vutil.common_argparse(parser) # pylint: disable=unused-variable # viscid.logger.setLevel(10) grids = [viscid.grid.Grid(time=t) for t in np.linspace(1.0, 10.0, 8)] dset = viscid.dataset.DatasetTemporal(*grids, basetime='1980-01-01') times = np.array([g.time for g in dset]) ################### # slice by integer test_slice(dset, np.s_[2], times[2]) # forward test_slice(dset, np.s_[4:], times[4:]) test_slice(dset, np.s_[2::3], times[2::3]) test_slice(dset, np.s_[:4], times[:4]) test_slice(dset, np.s_[:5:2], times[:5:2]) test_slice(dset, np.s_[2:5:2], times[2:5:2]) test_slice(dset, np.s_[6:5:1], times[6:5:1]) # backward test_slice(dset, np.s_[4::-1], times[4::-1]) test_slice(dset, np.s_[:4:-1], times[:4:-1]) test_slice(dset, np.s_[:4:-2], times[:4:-2]) test_slice(dset, np.s_[2:5:-2], times[2:5:-2]) test_slice(dset, np.s_[6:4:-1], times[6:4:-1]) # forward test_slice(dset, '4:', times[4:]) test_slice(dset, '2::3', times[2::3]) test_slice(dset, ':4', times[:4]) test_slice(dset, ':5:2', times[:5:2]) test_slice(dset, '2:5:2', times[2:5:2]) test_slice(dset, '6:5:1', times[6:5:1]) # backward test_slice(dset, '4::-1', times[4::-1]) test_slice(dset, ':4:-1', times[:4:-1]) test_slice(dset, ':4:-2', times[:4:-2]) test_slice(dset, '2:5:-2', times[2:5:-2]) test_slice(dset, '6:4:-1', times[6:4:-1]) ################# # slice by float # Note: times = [ 1. 2.28571429 3.57142857 4.85714286 # 6.14285714 7.42857143 8.71428571 10. ] test_slice(dset, np.s_['4.0f'], times[2]) test_slice(dset, np.s_['4.0f':], times[3:]) test_slice(dset, np.s_['4.0f'::2], times[3::2]) test_slice(dset, np.s_[:'4.0f':2], times[:3:2]) test_slice(dset, np.s_['2.0f':'7.8f'], times[1:6]) test_slice(dset, np.s_['2.0f':'7.8f':2], times[1:6:2]) test_slice(dset, np.s_['7.8f':'2.0f':-1], times[5:0:-1]) test_slice(dset, np.s_['7.8f':'2.0f':-1], times[5:1:-1], val_endpoint=False) test_slice(dset, np.s_['7.8f':'2.0f':-2], times[5:0:-2]) test_slice(dset, np.s_['7.8f':'2.0f':-2], times[5:1:-2], val_endpoint=False) test_slice(dset, np.s_['3.4f':'7.3f'], times[2:5]) test_slice(dset, np.s_['3.4f':'7.3f'], times[1:6], interior=True) test_slice(dset, np.s_['2.4f':'2.5f'], times[2:2]) test_slice(dset, np.s_['2.1f':'2.5f'], times[1:2]) test_slice(dset, np.s_['2.1f':'2.5f'], times[1:1], val_endpoint=False) test_slice(dset, np.s_['2.3f':'2.5f'], times[1:3], interior=True) ################ # slice by imag test_slice(dset, np.s_[4.0j], times[2]) test_slice(dset, np.s_[4.0j:], times[3:]) test_slice(dset, np.s_[4.0j::2], times[3::2]) test_slice(dset, np.s_[:4.0j:2], times[:3:2]) test_slice(dset, np.s_[2.0j:7.8j], times[1:6]) test_slice(dset, np.s_[2.0j:7.8j:2], times[1:6:2]) test_slice(dset, np.s_[7.8j:2.0j:-1], times[5:0:-1]) test_slice(dset, np.s_[7.8j:2.0j:-1], times[5:1:-1], val_endpoint=False) test_slice(dset, np.s_[7.8j:2.0j:-2], times[5:0:-2]) test_slice(dset, np.s_[7.8j:2.0j:-2], times[5:1:-2], val_endpoint=False) test_slice(dset, np.s_[3.4j:7.3j], times[2:5]) test_slice(dset, np.s_[3.4j:7.3j], times[1:6], interior=True) test_slice(dset, np.s_[2.4j:2.5j], times[2:2]) test_slice(dset, np.s_[2.1j:2.5j], times[1:2]) test_slice(dset, np.s_[2.1j:2.5j], times[1:1], val_endpoint=False) test_slice(dset, np.s_[2.3j:2.5j], times[1:3], interior=True) #################### # slice by imag str test_slice(dset, np.s_['4.0j'], times[2]) test_slice(dset, np.s_['4.0j':], times[3:]) test_slice(dset, np.s_['4.0j'::2], times[3::2]) test_slice(dset, np.s_[:'4.0j':2], times[:3:2]) test_slice(dset, np.s_['2.0j':'7.8j'], times[1:6]) test_slice(dset, np.s_['2.0j':'7.8j':2], times[1:6:2]) test_slice(dset, np.s_['7.8j':'2.0j':-1], times[5:0:-1]) test_slice(dset, np.s_['7.8j':'2.0j':-1], times[5:1:-1], val_endpoint=False) test_slice(dset, np.s_['7.8j':'2.0j':-2], times[5:0:-2]) test_slice(dset, np.s_['7.8j':'2.0j':-2], times[5:1:-2], val_endpoint=False) test_slice(dset, np.s_['3.4j':'7.3j'], times[2:5]) test_slice(dset, np.s_['3.4j':'7.3j'], times[1:6], interior=True) test_slice(dset, np.s_['2.4j':'2.5j'], times[2:2]) test_slice(dset, np.s_['2.1j':'2.5j'], times[1:2]) test_slice(dset, np.s_['2.1j':'2.5j'], times[1:1], val_endpoint=False) test_slice(dset, np.s_['2.3j':'2.5j'], times[1:3], interior=True) ############################ # slice by deprecated float viscid.logger.info("testing deprecated slice-by-location") test_slice(dset, np.s_['4.0'], times[2]) test_slice(dset, np.s_['4.0':], times[3:]) test_slice(dset, np.s_['4.0'::2], times[3::2]) test_slice(dset, np.s_[:'4.0':2], times[:3:2]) test_slice(dset, np.s_['2.0':'7.8'], times[1:6]) test_slice(dset, np.s_['2.0':'7.8':2], times[1:6:2]) test_slice(dset, np.s_['7.8':'2.0':-1], times[5:0:-1]) test_slice(dset, np.s_['7.8':'2.0':-1], times[5:1:-1], val_endpoint=False) test_slice(dset, np.s_['7.8':'2.0':-2], times[5:0:-2]) test_slice(dset, np.s_['7.8':'2.0':-2], times[5:1:-2], val_endpoint=False) test_slice(dset, np.s_['3.4':'7.3'], times[2:5]) test_slice(dset, np.s_['3.4':'7.3'], times[1:6], interior=True) test_slice(dset, np.s_['2.4':'2.5'], times[2:2]) test_slice(dset, np.s_['2.1':'2.5'], times[1:2]) test_slice(dset, np.s_['2.1':'2.5'], times[1:1], val_endpoint=False) test_slice(dset, np.s_['2.3':'2.5'], times[1:3], interior=True) viscid.logger.info("done testing deprecated slice-by-location") #################### # slice by datetime test_slice(dset, np.s_['1980-01-01T00:00:03.0':'1980-01-01T00:00:07.8'], times[2:6]) test_slice(dset, np.s_['UT1980-01-01T00:00:03.0:UT1980-01-01T00:00:07.8'], times[2:6]) test_slice(dset, np.s_['T1980-01-01T00:00:03.0:T1980-01-01T00:00:07.8'], times[2:6]) test_slice(dset, np.s_['1980-01-01T00:00:07.8':'1980-01-01T00:00:03.0':-2], times[5:1:-2]) ##################### # slice by timedelta test_slice(dset, np.s_['00:03.0':'00:07.8'], times[2:6]) test_slice(dset, np.s_['T00:03.0:T00:07.8'], times[2:6]) ############################# # slice by a few mixed types test_slice(dset, np.s_['UT1980-01-01T00:00:03.0:T00:07.8'], times[2:6]) test_slice(dset, np.s_['3f:T00:07.8'], times[2:6]) test_slice(dset, np.s_['3:T00:07.8'], times[3:6]) assert dset.tslc_range('2:5') == (3.5714285714285716, 7.4285714285714288) assert dset.tslc_range('2.3f:2.5f') == (2.3, 2.5) assert dset.tslc_range('UT1980-01-01T00:00:03.0:' 'UT1980-01-01T00:00:07.8') == (3.0, 7.8) t = viscid.linspace_datetime64('2010-01-01T12:00:00', '2010-01-01T15:00:00', 8) x = np.linspace(-1, 1, 12) fld = viscid.zeros([t, x], crd_names='tx', center='node') assert fld[:'2010-01-01T13:30:00'].shape == (4, 12) fld = viscid.zeros([t, x], crd_names='tx', center='cell') assert fld[:'2010-01-01T13:30:00'].shape == (4, 11) t = viscid.linspace_datetime64('2010-01-01T12:00:00', '2010-01-01T15:00:00', 8) t = t - t[0] x = np.linspace(-1, 1, 12) fld = viscid.zeros([t, x], crd_names='tx', center='node') assert fld[:'01:30:00'].shape == (4, 12) fld = viscid.zeros([t, x], crd_names='tx', center='cell') assert fld[:'01:30:00'].shape == (4, 11) return 0