Exemple #1
0
 def _get_spacing(self):
     # Find Network spacing
     P12 = self["throat.conns"]
     C12 = self["pore.coords"][P12]
     mag = np.linalg.norm(np.diff(C12, axis=1), axis=2)
     unit_vec = np.around(np.squeeze(np.diff(C12, axis=1)) / mag, decimals=14)
     spacing = [0, 0, 0]
     dims = topotools.dimensionality(self)
     # Ensure vectors point in n-dims unique directions
     c = {tuple(row): 1 for row in unit_vec}
     mag = np.atleast_1d(mag.squeeze()).astype(float)
     if len(c.keys()) > sum(dims):
         raise Exception(
             "Spacing is undefined when throats point in more directions"
             " than network has dimensions."
         )
     for ax in [0, 1, 2]:
         if dims[ax]:
             inds = np.where(unit_vec[:, ax] == unit_vec[:, ax].max())[0]
             temp = np.unique(mag[inds])
             if not np.allclose(temp, temp[0]):
                 raise Exception("A unique value of spacing could not be found.")
             spacing[ax] = temp[0]
     self.settings['spacing'] = spacing
     return np.array(spacing)
Exemple #2
0
 def _label_surface_pores(self):
     r"""
     """
     hits = np.zeros_like(self.Ps, dtype=bool)
     dims = topotools.dimensionality(self)
     mn = np.amin(self["pore.coords"], axis=0)
     mx = np.amax(self["pore.coords"], axis=0)
     for ax in [0, 1, 2]:
         if dims[ax]:
             hits += self["pore.coords"][:, ax] <= mn[ax]
             hits += self["pore.coords"][:, ax] >= mx[ax]
     self["pore.surface"] = hits
Exemple #3
0
def label_surface_nodes(network):
    r"""
    """
    hits = np.zeros_like(network.Ps, dtype=bool)
    dims = dimensionality(network)
    mn = np.amin(network["vert.coords"], axis=0)
    mx = np.amax(network["vert.coords"], axis=0)
    for ax in np.where(dims)[0]:
        if dims[ax]:
            hits += network["vert.coords"][:, ax] <= mn[ax]
            hits += network["vert.coords"][:, ax] >= mx[ax]
    network["vert.surface"] = hits
    return network
Exemple #4
0
 def __init__(self, template, spacing=[1, 1, 1], **kwargs):
     template = np.atleast_3d(template)
     super().__init__(shape=template.shape, spacing=spacing, **kwargs)
     coords = np.unravel_index(range(template.size), template.shape)
     self['pore.template_coords'] = np.vstack(coords).T
     self['pore.template_indices'] = self.Ps
     topotools.trim(network=self, pores=template.flatten() == 0)
     # Add "internal_surface" label to "fake" surface pores!
     ndims = topotools.dimensionality(self).sum()
     max_neighbors = 6 if ndims == 3 else 4
     num_neighbors = np.diff(self.get_adjacency_matrix(fmt="csr").indptr)
     mask_surface = self["pore.surface"]
     mask_internal_surface = (num_neighbors < max_neighbors) & ~mask_surface
     self.set_label("pore.internal_surface", pores=mask_internal_surface)
Exemple #5
0
def label_faces(network, threshold=0.05):
    r"""
    Label the vertices sitting on the faces of the domain in accordance with
    the conventions used for cubic etc.
    """
    dims = dimensionality(network)
    coords = np.around(network['vert.coords'], decimals=10)
    min_labels = ['front', 'left', 'bottom']
    max_labels = ['back', 'right', 'top']
    min_coords = np.amin(coords, axis=0)
    max_coords = np.amax(coords, axis=0)
    for ax in np.where(dims)[0]:
        network['vert.' + min_labels[ax]] = coords[:, ax] <= threshold*min_coords[ax]
        network['vert.' + max_labels[ax]] = coords[:, ax] >= (1-threshold)*max_coords[ax]
    return network
Exemple #6
0
def get_spacing(network):
    r"""
    Determine spacing of a cubic network

    Parameters
    ----------
    network : dictionary
        A network dictionary containing 'vert.coords' and 'edge.conns'

    Returns
    -------
    spacing : ndarray
        An array containing the spacing between vertices in each direction

    Notes
    -----
    This function only works on simple cubic networks with no boundary
    vertices. If a unique spacing cannot be found in each direction,
    and/or the edges are not all oriented perpendicularly, exceptions
    will be raised.

    """
    from openpnm.topotools import dimensionality
    # Find Network spacing
    P12 = network["edge.conns"]
    C12 = network["vert.coords"][P12]
    mag = np.linalg.norm(np.diff(C12, axis=1), axis=2)
    unit_vec = np.around(np.squeeze(np.diff(C12, axis=1)) / mag, decimals=14)
    spacing = [0, 0, 0]
    dims = dimensionality(coords=network['vert.coords'])
    # Ensure vectors point in n-dims unique directions
    c = {tuple(row): 1 for row in unit_vec}
    mag = np.atleast_1d(mag.squeeze()).astype(float)
    if len(c.keys()) > sum(dims):
        raise Exception(
            "Spacing is undefined when throats point in more directions"
            " than network has dimensions."
        )
    for ax in [0, 1, 2]:
        if dims[ax]:
            inds = np.where(unit_vec[:, ax] == unit_vec[:, ax].max())[0]
            temp = np.unique(mag[inds])
            if not np.allclose(temp, temp[0]):
                raise Exception("A unique value of spacing could not be found.")
            spacing[ax] = temp[0]
    return np.array(spacing)
Exemple #7
0
 def _get_spacing(self):
     # Find Network spacing
     P12 = self['throat.conns']
     C12 = self['pore.coords'][P12]
     mag = np.linalg.norm(np.diff(C12, axis=1), axis=2)
     unit_vec = sp.around(sp.squeeze(np.diff(C12, axis=1))/mag, decimals=14)
     spacing = [0, 0, 0]
     dims = topotools.dimensionality(self)
     # Ensure vectors point in n-dims unique directions
     c = {tuple(row): 1 for row in unit_vec}
     if len(c.keys()) > sum(dims):
         raise Exception('Spacing is undefined when throats point in ' +
                         'more directions than network has dimensions')
     mag = sp.float64(mag.squeeze())
     for ax in [0, 1, 2]:
         if dims[ax]:
             inds = sp.where(unit_vec[:, ax] == unit_vec[:, ax].max())[0]
             temp = sp.unique(mag[inds])
             if not sp.allclose(temp, temp[0]):
                 raise Exception('A unique value of spacing could not be found')
             else:
                 spacing[ax] = temp[0]
     return sp.array(spacing)
Exemple #8
0
def plot_connections(network,
                     throats=None,
                     ax=None,
                     size_by=None,
                     color_by=None,
                     cmap='jet',
                     color='b',
                     alpha=1.0,
                     linestyle='solid',
                     linewidth=1,
                     **kwargs):  # pragma: no cover
    r"""
    Produce a 3D plot of the network topology.

    This shows how throats connect for quick visualization without having
    to export data to veiw in Paraview.

    Parameters
    ----------
    network : GenericNetwork
        The network whose topological connections to plot
    throats : array_like (optional)
        The list of throats to plot if only a sub-sample is desired.  This is
        useful for inspecting a small region of the network.  If no throats are
        specified then all throats are shown.
    fig : Matplotlib figure handle and line property arguments (optional)
        If a ``fig`` is supplied, then the topology will be overlaid on this
        plot.  This makes it possible to combine coordinates and connections,
        and to color throats differently for instance.
    size_by : array_like (optional)
        An ndarray of throat values (e.g. alg['throat.rate']).  These
        values are used to scale the ``linewidth``, so if the lines are too
        thin, then increase ``linewidth``.
    color_by : str or array_like (optional)
        An ndarray of throat values (e.g. alg['throat.rate']).
    cmap : str or cmap object (optional)
        The matplotlib colormap to use if specfying a throat property
        for ``color_by``
    color : str, optional (optional)
        A matplotlib named color (e.g. 'r' for red).
    alpha : float (optional)
        The transparency of the lines, with 1 being solid and 0 being invisible
    linestyle : str (optional)
        Can be one of {'solid', 'dashed', 'dashdot', 'dotted'}.  Default is
        'solid'.
    linewidth : float (optional)
        Controls the thickness of drawn lines.  Is used to scale the thickness
        if ``size_by`` is given. Default is 1. If a value is provided for
        ``size_by`` then they are used to scale the ``linewidth``.
    **kwargs : dict
        All other keyword arguments are passed on to the ``Line3DCollection``
        class of matplotlib, so check their documentation for additional
        formatting options.

    Returns
    -------
    lc : LineCollection or Line3DCollection
        Matplotlib object containing the lines representing the throats.

    Notes
    -----
    To create a single plot containing both pore coordinates and throats,
    consider creating an empty figure and then pass the ``ax`` object as
    an argument to ``plot_connections`` and ``plot_coordinates``.
    Otherwise, each call to either of these methods creates a new figure.

    See Also
    --------
    plot_coordinates

    Examples
    --------
    >>> import openpnm as op
    >>> import matplotlib as mpl
    >>> import matplotlib.pyplot as plt
    >>> mpl.use('Agg')
    >>> pn = op.network.Cubic(shape=[10, 10, 3])
    >>> pn.add_boundary_pores()
    >>> Ts = pn.throats('*boundary', mode='not')        # find internal throats
    >>> fig, ax = plt.subplots()                        # create empty figure
    >>> _ = op.topotools.plot_connections(network=pn,
    ...                                   throats=Ts)   # plot internal throats
    >>> Ts = pn.throats('*boundary')                    # find boundary throats
    >>> _ = op.topotools.plot_connections(network=pn,
    ...                                   throats=Ts,
    ...                                   ax=ax,
    ...                                   color='r')    # plot boundary throats in red

    """
    import matplotlib.pyplot as plt
    from matplotlib import cm
    from matplotlib import colors as mcolors
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.collections import LineCollection
    from mpl_toolkits.mplot3d.art3d import Line3DCollection
    from openpnm.topotools import dimensionality

    Ts = network.Ts if throats is None else network._parse_indices(throats)
    dim = dimensionality(network)
    ThreeD = True if dim.sum() == 3 else False
    # Add a dummy axis for 1D networks
    if dim.sum() == 1:
        dim[np.argwhere(~dim)[0]] = True

    if "fig" in kwargs.keys():
        raise Exception("'fig' argument is deprecated, use 'ax' instead.")
    if ax is None:
        fig, ax = plt.subplots()
    else:
        # The next line is necessary if ax was created using plt.subplots()
        fig, ax = ax.get_figure(), ax.get_figure().gca()
    if ThreeD and ax.name != '3d':
        fig.delaxes(ax)
        ax = fig.add_subplot(111, projection='3d')

    # Collect coordinates
    Ps = np.unique(network['throat.conns'][Ts])
    X, Y, Z = network['pore.coords'][Ps].T
    xyz = network["pore.coords"][:, dim]
    P1, P2 = network["throat.conns"][Ts].T
    throat_pos = np.column_stack((xyz[P1], xyz[P2])).reshape((Ts.size, 2, dim.sum()))

    # Deal with optional style related arguments
    if 'c' in kwargs.keys():
        color = kwargs.pop('c')
    color = mcolors.to_rgb(color) + tuple([alpha])
    # Override colors with color_by if given
    if color_by is not None:
        color = cm.get_cmap(name=cmap)(color_by / color_by.max())
        color[:, 3] = alpha
    if size_by is not None:
        linewidth = size_by / size_by.max() * linewidth

    if ThreeD:
        lc = Line3DCollection(throat_pos, colors=color, cmap=cmap,
                              linestyles=linestyle, linewidths=linewidth,
                              antialiaseds=np.ones_like(network.Ts), **kwargs)
    else:
        lc = LineCollection(throat_pos, colors=color, cmap=cmap,
                            linestyles=linestyle, linewidths=linewidth,
                            antialiaseds=np.ones_like(network.Ts), **kwargs)
    ax.add_collection(lc)

    _scale_axes(ax=ax, X=X, Y=Y, Z=Z)
    _label_axes(ax=ax, X=X, Y=Y, Z=Z)
    fig.tight_layout()

    return lc
Exemple #9
0
def plot_networkx(network,
                  plot_throats=True,
                  labels=None,
                  colors=None,
                  scale=1,
                  ax=None,
                  alpha=1.0):  # pragma: no cover
    r"""
    Creates a pretty 2d plot for 2d OpenPNM networks.

    Parameters
    ----------
    network : GenericNetwork
    plot_throats : bool, optional
        Plots throats as well as pores, if True.
    labels : list, optional
        List of OpenPNM labels
    colors : list, optional
        List of corresponding colors to the given `labels`.
    scale : float, optional
        Scale factor for size of pores.
    ax : matplotlib.Axes, optional
        Matplotlib axes object
    alpha: float, optional
        Transparency value, 1 is opaque and 0 is transparent

    """
    import matplotlib.pyplot as plt
    from matplotlib.collections import PathCollection
    from networkx import Graph, draw_networkx_nodes, draw_networkx_edges
    from openpnm.topotools import dimensionality

    dims = dimensionality(network)
    if dims.sum() > 2:
        raise Exception("NetworkX plotting only works for 2D networks.")
    temp = network['pore.coords'].T[dims].squeeze()
    if dims.sum() == 1:
        x = temp
        y = np.zeros_like(x)
    if dims.sum() == 2:
        x, y = temp
    try:
        node_size = scale * network['pore.diameter']
    except KeyError:
        node_size = np.ones_like(x) * scale * 0.5
    G = Graph()
    pos = {network.Ps[i]: [x[i], y[i]] for i in range(network.Np)}
    if not np.isfinite(node_size).all():
        raise Exception('nan/inf values found in network["pore.diameter"]')
    node_color = np.array(['k'] * len(network.Ps))

    if labels:
        if not isinstance(labels, list):
            labels = [labels]
        if not isinstance(colors, list):
            colors = [colors]
        if len(labels) != len(colors):
            raise Exception('len(colors) must be equal to len(labels)!')
        for label, color in zip(labels, colors):
            node_color[network.pores(label)] = color

    if ax is None:
        fig, ax = plt.subplots()
    ax.set_aspect('equal', adjustable='datalim')
    offset = node_size.max() * 0.5
    ax.set_xlim((x.min() - offset, x.max() + offset))
    ax.set_ylim((y.min() - offset, y.max() + offset))
    ax.axis("off")

    # Keep track of already plotted nodes
    temp = [id(item) for item in ax.collections if isinstance(item, PathCollection)]

    # Plot pores
    gplot = draw_networkx_nodes(G, ax=ax, pos=pos, nodelist=network.Ps.tolist(),
                                alpha=alpha, node_color="w", edgecolors=node_color,
                                node_size=node_size)
    # (Optionally) Plot throats
    if plot_throats:
        draw_networkx_edges(G, pos=pos, edge_color='k', alpha=alpha,
                            edgelist=network['throat.conns'].tolist(), ax=ax)

    spi = 2700  # 1250 was obtained by trial and error
    figwidth, figheight = ax.get_figure().get_size_inches()
    figsize_ratio = figheight / figwidth
    data_ratio = ax.get_data_ratio()
    corr = min(figsize_ratio / data_ratio, 1)
    xrange = np.ptp(ax.get_xlim())
    markersize = np.atleast_1d((corr*figwidth)**2 / xrange**2 * node_size**2 * spi)
    for item in ax.collections:
        if isinstance(item, PathCollection) and id(item) not in temp:
            item.set_sizes(markersize)

    return gplot
Exemple #10
0
def plot_coordinates(network,
                     pores=None,
                     ax=None,
                     size_by=None,
                     color_by=None,
                     cmap='jet',
                     color='r',
                     alpha=1.0,
                     marker='o',
                     markersize=10,
                     **kwargs):  # pragma: no cover
    r"""
    Produce a 3D plot showing specified pore coordinates as markers.

    Parameters
    ----------
    network : GenericNetwork
        The network whose topological connections to plot.
    pores : array_like (optional)
        The list of pores to plot if only a sub-sample is desired. This is
        useful for inspecting a small region of the network. If no pores
        are specified then all are shown.
    ax : Matplotlib axis handle
        If ``ax`` is supplied, then the coordinates will be overlaid.
        This enables the plotting of multiple different sets of pores as
        well as throat connections from ``plot_connections``.
    size_by : str or array_like
        An ndarray of pore values (e.g. alg['pore.concentration']). These
        values are normalized by scaled by ``markersize``.
    color_by : str or array_like
        An ndarray of pore values (e.g. alg['pore.concentration']).
    cmap : str or cmap object
        The matplotlib colormap to use if specfying a pore property
        for ``color_by``
    color : str
        A matplotlib named color (e.g. 'r' for red).
    alpha : float
        The transparency of the lines, with 1 being solid and 0 being invisible
    marker : 's'
        The marker to use.  The default is a circle.  Options are explained
        `here <https://matplotlib.org/3.2.1/api/markers_api.html>`_
    markersize : scalar
        Controls size of marker, default is 1.0.  This value is used to scale
        the ``size_by`` argument if given.
    **kwargs
        All other keyword arguments are passed on to the ``scatter``
        function of matplotlib, so check their documentation for additional
        formatting options.

    Returns
    -------
    pc : PathCollection
        Matplotlib object containing the markers representing the pores.

    Notes
    -----
    To create a single plot containing both pore coordinates and throats,
    consider creating an empty figure and then pass the ``ax`` object as
    an argument to ``plot_connections`` and ``plot_coordinates``.
    Otherwise, each call to either of these methods creates a new figure.

    See Also
    --------
    plot_connections

    Examples
    --------
    >>> import openpnm as op
    >>> import matplotlib as mpl
    >>> import matplotlib.pyplot as plt
    >>> mpl.use('Agg')
    >>> pn = op.network.Cubic(shape=[10, 10, 3])
    >>> pn.add_boundary_pores()
    >>> Ps = pn.pores('internal')                       # find internal pores
    >>> fig, ax = plt.subplots()                        # create empty figure
    >>> _ = op.topotools.plot_coordinates(network=pn,
    ...                                   pores=Ps,
    ...                                   color='b',
    ...                                   ax=ax)        # plot internal pores
    >>> Ps = pn.pores('*boundary')                      # find boundary pores
    >>> _ = op.topotools.plot_coordinates(network=pn,
    ...                                   pores=Ps,
    ...                                   color='r',
    ...                                   ax=ax)        # plot boundary pores in red

    """
    import matplotlib.pyplot as plt
    from matplotlib import cm
    from mpl_toolkits.mplot3d import Axes3D
    from openpnm.topotools import dimensionality

    Ps = network.Ps if pores is None else network._parse_indices(pores)

    dim = dimensionality(network)
    ThreeD = True if dim.sum() == 3 else False
    # Add a dummy axis for 1D networks
    if dim.sum() == 1:
        dim[np.argwhere(~dim)[0]] = True
    # Add 2 dummy axes for 0D networks (1 pore only)
    if dim.sum() == 0:
        dim[[0, 1]] = True

    if "fig" in kwargs.keys():
        raise Exception("'fig' argument is deprecated, use 'ax' instead.")
    if ax is None:
        fig, ax = plt.subplots()
    else:
        # The next line is necessary if ax was created using plt.subplots()
        fig, ax = ax.get_figure(), ax.get_figure().gca()
    if ThreeD and ax.name != '3d':
        fig.delaxes(ax)
        ax = fig.add_subplot(111, projection='3d')

    # Collect specified coordinates
    X, Y, Z = network['pore.coords'][Ps].T
    # The bounding box for fig is the entire ntwork (to fix the problem with
    # overwriting figures' axes lim)
    Xl, Yl, Zl = network['pore.coords'].T

    # Parse formatting kwargs
    if 'c' in kwargs.keys():
        color = kwargs.pop('c')
    if 's' in kwargs.keys():
        markersize = kwargs.pop('s')
    if color_by is not None:
        color = cm.get_cmap(name=cmap)(color_by / color_by.max())
    if size_by is not None:
        markersize = size_by / size_by.max() * markersize

    if ThreeD:
        sc = ax.scatter(X, Y, Z, c=color, s=markersize, marker=marker, alpha=alpha, **kwargs)
        _scale_axes(ax=ax, X=Xl, Y=Yl, Z=Zl)
    else:
        _X, _Y = np.column_stack((X, Y, Z))[:, dim].T
        sc = ax.scatter(_X, _Y, c=color, s=markersize, marker=marker, alpha=alpha, **kwargs)
        _scale_axes(ax=ax, X=Xl, Y=Yl, Z=np.zeros_like(Yl))

    _label_axes(ax=ax, X=Xl, Y=Yl, Z=Zl)
    fig.tight_layout()

    return sc
Exemple #11
0
def plot_coordinates(network,
                     pores=None,
                     fig=None,
                     size_by=None,
                     color_by=None,
                     cmap='jet',
                     color='r',
                     alpha=1.0,
                     marker='o',
                     markersize=10,
                     **kwargs):
    r"""
    Produce a 3D plot showing specified pore coordinates as markers.

    Parameters
    ----------
    network : OpenPNM Network Object
        The network whose topological connections to plot.
    pores : array_like (optional)
        The list of pores to plot if only a sub-sample is desired. This is
        useful for inspecting a small region of the network. If no pores
        are specified then all are shown.
    fig : Matplotlib figure handle
        If a ``fig`` is supplied, then the coordinates will be overlaid.
        This enables the plotting of multiple different sets of pores as
        well as throat connections from ``plot_connections``.
    size_by : str or array_like
        An ND-array of pore values (e.g. alg['pore.concentration']). These
        values are normalized by scaled by ``markersize``.
    color_by : str or array_like
        An ND-array of pore values (e.g. alg['pore.concentration']).
    cmap : str or cmap object
        The matplotlib colormap to use if specfying a pore property
        for ``color_by``
    color : str
        A matplotlib named color (e.g. 'r' for red).
    alpha : float
        The transparency of the lines, with 1 being solid and 0 being invisible
    marker : 's'
        The marker to use.  The default is a circle.  Options are explained
        `here <https://matplotlib.org/3.2.1/api/markers_api.html>`_
    markersize : scalar
        Controls size of marker, default is 1.0.  This value is used to scale
        the ``size_by`` argument if given.
    **kwargs
        All other keyword arguments are passed on to the ``scatter``
        function of matplotlib, so check their documentation for additional
        formatting options.

    Notes
    -----
    The figure handle returned by this method can be passed into
    ``plot_connections`` to create a plot that combines pore coordinates
    and throat connections, and vice versa.

    See Also
    --------
    plot_connections

    Examples
    --------
    >>> import openpnm as op
    >>> import matplotlib as mpl
    >>> mpl.use('Agg')
    >>> pn = op.network.Cubic(shape=[10, 10, 3])
    >>> pn.add_boundary_pores()
    >>> Ps = pn.pores('internal')
    >>> # Create figure showing internal pores
    >>> fig = op.topotools.plot_coordinates(pn, pores=Ps, c='b')
    >>> Ps = pn.pores('*boundary')
    >>> # Pass existing fig back into function to plot boundary pores
    >>> fig = op.topotools.plot_coordinates(pn, pores=Ps, fig=fig, color='r')

    """
    import matplotlib.pyplot as plt
    from matplotlib import cm
    from mpl_toolkits.mplot3d import Axes3D
    from openpnm.topotools import dimensionality

    Ps = network.Ps if pores is None else network._parse_indices(pores)

    dim = dimensionality(network)
    ThreeD = True if dim.sum() == 3 else False
    # Add a dummy axis for 1D networks
    if dim.sum() == 1:
        dim[np.argwhere(~dim)[0]] = True
    # Add 2 dummy axes for 0D networks (1 pore only)
    if dim.sum() == 0:
        dim[[0, 1]] = True

    fig = plt.figure() if fig is None else fig
    ax = fig.gca()
    if ThreeD and ax.name != '3d':
        fig.delaxes(ax)
        ax = fig.add_subplot(111, projection='3d')

    # Collect specified coordinates
    X, Y, Z = network['pore.coords'][Ps].T
    # The bounding box for fig is the entire ntwork (to fix the problem with
    # overwriting figures' axes lim)
    Xl, Yl, Zl = network['pore.coords'].T

    # Parse formatting kwargs
    if 'c' in kwargs.keys():
        color = kwargs.pop('c')
    if 's' in kwargs.keys():
        markersize = kwargs.pop('s')
    if color_by is not None:
        color = cm.get_cmap(name=cmap)(color_by / color_by.max())
    if size_by is not None:
        markersize = size_by / size_by.max() * markersize

    if ThreeD:
        ax.scatter(X,
                   Y,
                   Z,
                   c=color,
                   s=markersize,
                   marker=marker,
                   alpha=alpha,
                   **kwargs)
        _scale_3d_axes(ax=ax, X=Xl, Y=Yl, Z=Zl, dimen=ThreeD)
    else:
        X_temp, Y_temp = np.column_stack((X, Y, Z))[:, dim].T
        ax.scatter(X_temp,
                   Y_temp,
                   c=color,
                   s=markersize,
                   marker=marker,
                   alpha=alpha,
                   **kwargs)
        _scale_3d_axes(ax=ax, X=Xl, Y=Yl, Z=np.zeros_like(Yl), dimen=ThreeD)

    _label_axes(ax=ax, X=Xl, Y=Yl, Z=Zl)

    return fig