def _diagnose(paths, cells): if cells is None: return import matplotlib.pyplot as plt import tramway.plot.mesh as mesh f = plt.figure() mesh.plot_delaunay(cells, axes=f.gca()) colors = 'bgkyr' for p, c in zip(paths, colors): x = cells.tesselation.cell_centers[p] plt.plot(x[:, 0], x[:, 1], c, linestyle='-', linewidth=3) plt.show()
def plot_cell_indices(self, tessellation, axes=None, voronoi_options=dict(), delaunay_options=None, aspect="equal", title=None, **kwargs): """ Plot the Voronoi graph and Voronoi cell indices. The placement of the text elements is not optimized. """ from tramway.plot.mesh import plot_delaunay, plot_voronoi, plot_cell_indices try: voronoi_options = dict(centroid_style=None, color="rrrr") | voronoi_options except TypeError: # Python < 3.9 voronoi_options, _options = ( dict(centroid_style=None, color="rrrr"), voronoi_options, ) voronoi_options.update(_options) if isinstance(tessellation, Analysis): tessellation = tessellation.data if isinstance(tessellation, Partition): sampling = tessellation tessellation = sampling.tessellation else: sampling = None if isinstance(tessellation, TimeLattice): if tessellation.spatial_mesh is None: raise TypeError("no tessellation found; only time segments") tessellation = tessellation.spatial_mesh if isinstance(tessellation, Voronoi): if sampling is None: sampling = Partition(locations, tessellation) else: raise TypeError("tessellation type not supported: {}".format( type(tessellation))) if delaunay_options: plot_delaunay(sampling, axes=axes, **delaunay_options) if voronoi_options: plot_voronoi(sampling, axes=axes, **voronoi_options) plot_cell_indices(tessellation, axes=axes, **kwargs) if title: axes.set_title(title) if aspect: axes.set_aspect(aspect)
def scalar_map_2d( cells, values, aspect=None, clim=None, figure=None, axes=None, linewidth=1, delaunay=False, colorbar=True, alpha=None, colormap=None, unit=None, clabel=None, xlim=None, ylim=None, try_fix_corners=True, return_patches=False, **kwargs ): """ Plot a 2D scalar map as a colourful image. Arguments: cells (Partition or FiniteElements): spatial description of the cells values (pandas.DataFrame or numpy.ndarray): feature value at each cell, that will be represented as a colour aspect (str): passed to :func:`~matplotlib.axes.Axes.set_aspect` clim (2-element sequence): passed to :func:`~matplotlib.cm.ScalarMappable.set_clim` figure (matplotlib.figure.Figure): figure handle axes (matplotlib.axes.Axes): axes handle linewidth (int): cell border line width delaunay (bool or dict): overlay the Delaunay graph; if ``dict``, options are passed to :func:`~tramway.plot.mesh.plot_delaunay` colorbar (bool or str or dict): add a colour bar; if ``dict``, options are passed to :func:`~matplotlib.pyplot.colorbar`; setting colorbar to '*nice*' allows to produce a colorbar close to the figure of the same size as the figure unit/clabel (str): colorbar label, usually the unit of displayed feature alpha (float): alpha value of the cells colormap (str): colormap name; see also https://matplotlib.org/users/colormaps.html xlim (2-element sequence): lower and upper x-axis bounds ylim (2-element sequence): lower and upper y-axis bounds return_patches (bool): returns `PatchCollection` patches and the corresponding bin indices. Extra keyword arguments are passed to :func:`~matplotlib.collections.PatchCollection`. """ coords = None if isinstance(values, pd.DataFrame): if values.shape[1] != 1: coords = values[[col for col in "xyzt" if col in values.columns]] values = values[[col for col in values.columns if col not in "xyzt"]] if values.shape[1] != 1: warn( "multiple parameters available; mapping first one only", UserWarning ) values = values.iloc[:, 0] # to Series # values = pd.to_numeric(values, errors='coerce') # parse Delaunay-related arguments if delaunay: if not isinstance(delaunay, dict): delaunay = {} if linewidth and "linewidth" not in delaunay: delaunay["linewidth"] = linewidth # turn the cells into polygons ids = [] polygons = [] if isinstance(cells, FiniteElements): ix, xy, ok = zip(*[(i, c.center, bool(c)) for i, c in cells.items()]) ix, xy, ok = np.array(ix), np.array(xy), np.array(ok) if not (coords is None or np.all(np.isclose(xy, coords))): # debug print("coordinates mismatch") print("in map:") print(xy) print("in cells:") print(coords) rp = bb = voronoi = None for c in range(xy.shape[0]): if ok[c]: vertices, voronoi, bb, rp = cell_to_polygon( c, xy, voronoi, bb, rp, True ) polygons.append(Polygon(vertices, True)) elif isinstance(cells, Partition) and isinstance(cells.tessellation, Voronoi): xy = cells.tessellation.cell_centers # copy/paste from below if not xlim or not ylim: xy_min, _, xy_max, _ = _bounding_box(cells, xy) if not xlim: xlim = (xy_min[0], xy_max[0]) if not ylim: ylim = (xy_min[1], xy_max[1]) ix = np.arange(xy.shape[0]) from tramway.tessellation.hexagon import HexagonalMesh from tramway.tessellation.kdtree import KDTreeMesh if isinstance(cells.tessellation, (RegularMesh, HexagonalMesh, KDTreeMesh)): vertices, cell_vertices, Av = ( cells.tessellation.vertices, cells.tessellation.cell_vertices, cells.tessellation.vertex_adjacency.tocsr(), ) else: try: vertices, cell_vertices, Av = box_voronoi_2d( cells.tessellation, xlim, ylim ) except AssertionError: warn("could not fix the borders", RuntimeWarning) vertices, cell_vertices, Av = ( cells.tessellation.vertices, cells.tessellation.cell_vertices, cells.tessellation.vertex_adjacency.tocsr(), ) try: ok = 0 < cells.location_count except (KeyboardInterrupt, SystemExit): raise except: print(traceback.format_exc()) ok = np.ones(ix.size, dtype=bool) if cells.tessellation.cell_label is not None: ok = np.logical_and(ok, 0 < cells.tessellation.cell_label) map_defined = np.zeros_like(ok) map_defined[values.index] = True ok[np.logical_not(map_defined)] = False ok[ok] = np.logical_not(np.isnan(values.loc[ix[ok]].values)) for i in ix[ok]: extra_polygons = cell_to_polygon_( i, vertices, cell_vertices, Av, xlim, ylim, try_fix_corners ) for extra_ix, extra_vs in extra_polygons: ids.append(extra_ix) polygons.append(Polygon(extra_vs, True)) else: _type = repr(type(cells)) if _type.endswith("'>"): _type = _type.split("'")[1] try: _nested_type = repr(type(cells.tessellation)) if _nested_type.endswith("'>"): _nested_type = _nested_type.split("'")[1] raise TypeError( "wrong type for `cells`: {}<{}>".format(_type, _nested_type) ) except AttributeError: raise TypeError("wrong type for `cells`: {}".format(_type)) if not ids: ids = ix[ok] scalar_map = values.loc[ids].values # print(np.nonzero(~ok)[0]) try: if np.any(np.isnan(scalar_map)): # print(np.nonzero(np.isnan(scalar_map))) msg = "NaN found" try: warn(msg, NaNWarning) except: print("warning: {}".format(msg)) scalar_map[np.isnan(scalar_map)] = 0 except TypeError: # help debug print(scalar_map) print(scalar_map.dtype) raise if figure is None: import matplotlib.pyplot as plt figure = plt.gcf() # before PatchCollection if axes is None: axes = figure.gca() # draw patches patch_kwargs = kwargs if alpha is not False: if alpha is None: alpha = 0.9 patch_kwargs["alpha"] = alpha if colormap is not None: cmap = patch_kwargs.get("cmap", None) if cmap is None: patch_kwargs["cmap"] = colormap elif colormap != cmap: warn( "both cmap and colormap arguments are passed with different values", RuntimeWarning, ) patches = PatchCollection(polygons, linewidth=linewidth, **patch_kwargs) patches.set_array(scalar_map) if clim is not None: patches.set_clim(clim) axes.add_collection(patches) obj = None if delaunay or isinstance(delaunay, dict): if not isinstance(delaunay, dict): delaunay = {} try: import tramway.plot.mesh as mesh obj = mesh.plot_delaunay(cells, centroid_style=None, axes=axes, **delaunay) except: import traceback traceback.print_exc() if not xlim or not ylim: xy_min, _, xy_max, _ = _bounding_box(cells, xy) if not xlim: xlim = (xy_min[0], xy_max[0]) if not ylim: ylim = (xy_min[1], xy_max[1]) axes.set_xlim(*xlim) axes.set_ylim(*ylim) if aspect is not None: axes.set_aspect(aspect) if colorbar: if colorbar == "nice": # make the colorbar closer to the plot and same size from mpl_toolkits.axes_grid1 import make_axes_locatable try: plt except NameError: import matplotlib.pyplot as plt try: gca_bkp = plt.gca() divider = make_axes_locatable(figure.gca()) cax = divider.append_axes("right", size="5%", pad=0.05) _colorbar = figure.colorbar(patches, cax=cax) plt.sca(gca_bkp) except AttributeError as e: warn(e.args[0], RuntimeWarning) else: if not isinstance(colorbar, dict): colorbar = {} try: _colorbar = figure.colorbar(patches, ax=axes, **colorbar) except AttributeError as e: warn(e.args[0], RuntimeWarning) if clabel: unit = clabel if unit: _colorbar.set_label(unit) if return_patches: return patches, np.asarray(ids) else: return obj
def scalar_map_2d(cells, values, aspect=None, clim=None, figure=None, axes=None, linewidth=1, delaunay=False, colorbar=True, alpha=None, colormap=None, unit=None, clabel=None, xlim=None, ylim=None, **kwargs): """ Plot a 2D scalar map as a colourful image. Arguments: cells (CellStats or Distributed): spatial description of the cells values (pandas.DataFrame or numpy.ndarray): feature value at each cell, that will be represented as a colour aspect (str): passed to :func:`~matplotlib.axes.Axes.set_aspect` clim (2-element sequence): passed to :func:`~matplotlib.cm.ScalarMappable.set_clim` figure (matplotlib.figure.Figure): figure handle axes (matplotlib.axes.Axes): axes handle linewidth (int): cell border line width delaunay (bool or dict): overlay the Delaunay graph; if ``dict``, options are passed to :func:`~tramway.core.plot.mesh.plot_delaunay` colorbar (bool or str or dict): add a colour bar; if ``dict``, options are passed to :func:`~matplotlib.pyplot.colorbar`; setting colorbar to '*nice*' allows to produce a colorbar close to the figure of the same size as the figure unit/clabel (str): colorbar label, usually the unit of displayed feature alpha (float): alpha value of the cells colormap (str): colormap name; see also https://matplotlib.org/users/colormaps.html xlim (2-element sequence): lower and upper x-axis bounds ylim (2-element sequence): lower and upper y-axis bounds Extra keyword arguments are passed to :func:`~matplotlib.collections.PatchCollection`. """ coords = None if isinstance(values, pd.DataFrame): if values.shape[1] != 1: coords = values[[col for col in 'xyzt' if col in values.columns]] values = values[[ col for col in values.columns if col not in 'xyzt' ]] if values.shape[1] != 1: warn('multiple parameters available; mapping first one only', UserWarning) values = values.iloc[:, 0] # to Series #values = pd.to_numeric(values, errors='coerce') # parse Delaunay-related arguments if delaunay: if not isinstance(delaunay, dict): delaunay = {} if linewidth and 'linewidth' not in delaunay: delaunay['linewidth'] = linewidth # turn the cells into polygons polygons = [] if isinstance(cells, Distributed): ix, xy, ok = zip(*[(i, c.center, bool(c)) for i, c in cells.items()]) ix, xy, ok = np.array(ix), np.array(xy), np.array(ok) if not (coords is None or np.all(np.isclose(xy, coords))): # debug print('coordinates mismatch') print('in map:') print(xy) print('in cells:') print(coords) rp = bb = voronoi = None for c in range(xy.shape[0]): if ok[c]: vertices, voronoi, bb, rp = cell_to_polygon( c, xy, voronoi, bb, rp, True) polygons.append(Polygon(vertices, True)) elif isinstance(cells, CellStats) and isinstance(cells.tessellation, Voronoi): Av = cells.tessellation.vertex_adjacency.tocsr() xy = cells.tessellation.cell_centers ix = np.arange(xy.shape[0]) try: ok = 0 < cells.location_count except (KeyboardInterrupt, SystemExit): raise except: print(traceback.format_exc()) ok = np.ones(ix.size, dtype=bool) if cells.tessellation.cell_label is not None: ok = np.logical_and(ok, 0 < cells.tessellation.cell_label) map_defined = np.zeros_like(ok) map_defined[values.index] = True ok[np.logical_not(map_defined)] = False ok[ok] = np.logical_not(np.isnan(values.loc[ix[ok]].values)) for i in ix[ok]: vs = cells.tessellation.cell_vertices[i].tolist() # order the vertices so that they draw a polygon v0 = v = vs[0] vs = set(vs) vertices = [] #vvs = [] # debug while True: vertices.append(cells.tessellation.vertices[v]) #vvs.append(v) vs.remove(v) if not vs: break ws = set(Av.indices[Av.indptr[v]:Av.indptr[v + 1]]) & vs if not ws: ws = set(Av.indices[Av.indptr[v0]:Av.indptr[v0 + 1]]) & vs if ws: vertices = vertices[::-1] else: #print((v, vs, vvs, [Av.indices[Av.indptr[v]:Av.indptr[v+1]] for v in vs])) warn( 'cannot find a path that connects all the vertices of a cell', RuntimeWarning) break v = ws.pop() # if vertices: vertices = np.vstack(vertices) polygons.append(Polygon(vertices, True)) else: _type = repr(type(cells)) if _type.endswith("'>"): _type = _type.split("'")[1] try: _nested_type = repr(type(cells.tessellation)) if _nested_type.endswith("'>"): _nested_type = _nested_type.split("'")[1] raise TypeError('wrong type for `cells`: {}<{}>'.format( _type, _nested_type)) except AttributeError: raise TypeError('wrong type for `cells`: {}'.format(_type)) scalar_map = values.loc[ix[ok]].values #print(np.nonzero(~ok)[0]) try: if np.any(np.isnan(scalar_map)): #print(np.nonzero(np.isnan(scalar_map))) msg = 'NaN found' try: warn(msg, NaNWarning) except: print('warning: {}'.format(msg)) scalar_map[np.isnan(scalar_map)] = 0 except TypeError: # help debug print(scalar_map) print(scalar_map.dtype) raise if figure is None: import matplotlib.pyplot as plt figure = plt.gcf() # before PatchCollection if axes is None: axes = figure.gca() # draw patches patch_kwargs = kwargs if alpha is not False: if alpha is None: alpha = .9 patch_kwargs['alpha'] = alpha if colormap is not None: cmap = patch_kwargs.get('cmap', None) if cmap is None: patch_kwargs['cmap'] = colormap elif colormap != cmap: warn( 'both cmap and colormap arguments are passed with different values', RuntimeWarning) patches = PatchCollection(polygons, linewidth=linewidth, **patch_kwargs) patches.set_array(scalar_map) if clim is not None: patches.set_clim(clim) axes.add_collection(patches) obj = None if delaunay or isinstance(delaunay, dict): try: import tramway.plot.mesh as mesh obj = mesh.plot_delaunay(cells, centroid_style=None, axes=axes, **delaunay) except: import traceback traceback.print_exc() if not xlim or not ylim: xy_min, _, xy_max, _ = _bounding_box(cells, xy) if not xlim: xlim = (xy_min[0], xy_max[0]) if not ylim: ylim = (xy_min[1], xy_max[1]) axes.set_xlim(*xlim) axes.set_ylim(*ylim) if aspect is not None: axes.set_aspect(aspect) if colorbar: if colorbar == 'nice': # make the colorbar closer to the plot and same size from mpl_toolkits.axes_grid1 import make_axes_locatable try: plt except NameError: import matplotlib.pyplot as plt try: gca_bkp = plt.gca() divider = make_axes_locatable(figure.gca()) cax = divider.append_axes("right", size="5%", pad=0.05) _colorbar = figure.colorbar(patches, cax=cax) plt.sca(gca_bkp) except AttributeError as e: warn(e.args[0], RuntimeWarning) else: if not isinstance(colorbar, dict): colorbar = {} try: _colorbar = figure.colorbar(patches, ax=axes, **colorbar) except AttributeError as e: warn(e.args[0], RuntimeWarning) if clabel: unit = clabel if unit: _colorbar.set_label(unit) return obj