13 Stimmen

Füllen AUSSERHALB des Polygons | Maskenfeldes, wenn die Indikatoren außerhalb einer kreisförmigen Begrenzung liegen?

Ich benutze plot(x,y,'r') um einen roten Kreis zu zeichnen. x und y sind Arrays, so dass, wenn sie als (x,y) gepaart und gezeichnet werden, alle Punkte eine Kreislinie bilden.

fill(x,y,'r') zeichnet einen roten Kreis, der rot ausgefüllt (oder eingefärbt) ist.

Wie kann ich den Kreis auf der Innenseite weiß lassen, aber außerhalb des Kreises bis zu den Achsengrenzen füllen?

Ich habe die Verwendung von fill_between(x_array, y1_array, y2_array, where) aber nach ein wenig spielen mit ihm ich glaube nicht, dass das für meine x,y Arrays funktionieren wird. Ich dachte an fill_between() außerhalb des Kreises und innerhalb eines Quadrats, das durch die Achsengrenzen definiert ist, aber ich glaube nicht, dass fill_between() fähig ist Ich bin mir sicher, dass ich daraus eine Art Integralproblem machen könnte, bei dem Delta x und Delta y gegen Null gehen, aber das will ich nicht.

Wenn jemand sehen kann, dass ich etwas übersehe mit fill_between() lassen Sie es mich bitte wissen.

Alles, was ich wirklich brauche, ist Maskierung aus Zahlen in einem 2D-Array, die sich außerhalb dieser Grenze des Kreises mit x und y erstellt, so dass, wenn das 2D-Array als ein Farbdiagramm oder Kontur angezeigt wird, innerhalb des Kreises wird das Bild, und außerhalb wird weiß-aus sein.

Kann dies stattdessen durch eine Maskierungstechnik des 2D-Arrays erreicht werden? Zum Beispiel durch die Verwendung von masked_where() ? Ich habe mich noch nicht damit befasst, werde es aber tun.

Irgendwelche Ideen? Danke

Bearbeiten 1: Hier ist, was ich habe die Erlaubnis zu zeigen, dass ich denke, wird mein Problem zu erklären.

from pylab import *
from matplotlib.path import Path
from matplotlib.patches import PathPatch

f=Figure()
a=f.add_subplot(111)

# x,y,z are 2d arrays

# sometimes i plot a color plot
# im = a.pcolor(x,y,z)
a.pcolor(x,y,z)

# sometimes i plot a contour
a.contour(x,y,z)

# sometimes i plot both using a.hold(True)

# here is the masking part.
# sometimes i just want to see the boundary drawn without masking
# sometimes i want to see the boundary drawn with masking inside of the boundary
# sometimes i want to see the boundary drawn with masking outside of the boundary

# depending on the vectors that define x_bound and y_bound, sometimes the boundary
# is a circle, sometimes it is not.

path=Path(vpath)
patch=PathPatch(path,facecolor='none')
a.add_patch(patch) # just plots boundary if anything has been previously plotted on a
if ('I want to mask inside'):
    patch.set_facecolor('white') # masks(whitens) inside if pcolor is currently on a,
    # but if contour is on a, the contour part is not whitened out. 
else: # i want to mask outside 
    im.set_clip_path(patch) # masks outside only when im = a.pcolor(x,y,z)
    # the following commands don't update any masking but they don't produce errors?
    # patch.set_clip_on(True)
    # a.set_clip_on(True)
    # a.set_clip_path(patch)

a.show()

16voto

Joe Kington Punkte 258905

Alles, was ich wirklich brauche, ist Zahlen in einem 2D-Array ausblenden, die die sich jenseits dieser Begrenzung des Kreises liegen, der mit x und y erstellt wurde, so dass wenn das 2D-Array als Farbdiagramm oder oder Kontur betrachtet wird, innerhalb des Kreises das Bild, und außerhalb wird ausgeblendet.

Sie haben zwei Möglichkeiten:

Zunächst könnten Sie ein maskiertes Array für die Bilder verwenden. Das ist zwar komplizierter, aber auch etwas sicherer. Um ein Array außerhalb eines Kreises zu maskieren, erstellen Sie eine Entfernungskarte vom Mittelpunkt aus und maskieren die Bereiche, in denen die Entfernung größer als der Radius ist.

Die einfachere Möglichkeit ist, die Bereiche außerhalb des Flecks mit im.set_clip_path() zu beschneiden, nachdem Sie das Bild geplottet haben.

Siehe dieses Beispiel aus der Matplotlib-Galerie . Leider kann dies bei einigen Achsen (nicht kartesischen Achsen) meiner Erfahrung nach ein wenig problematisch sein. In allen anderen Fällen sollte es jedoch perfekt funktionieren.

Edit: Im Übrigen, So tun Sie, worum Sie ursprünglich gebeten haben : Plotten Sie ein Polygon mit einem Loch darin. Wenn Sie jedoch nur ein Bild maskieren wollen, sind Sie mit einer der beiden obigen Optionen besser bedient.

Edit2: Nur um ein kurzes Beispiel für beide Möglichkeiten zu geben...

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

def main():
    # Generate some random data
    nx, ny = 100, 100
    data = np.random.random((ny,nx))

    # Define a circle in the center of the data with a radius of 20 pixels
    radius = 20
    center_x = nx // 2
    center_y = ny // 2

    plot_masked(data, center_x, center_y, radius)
    plot_clipped(data, center_x, center_y, radius)
    plt.show()

def plot_masked(data, center_x, center_y, radius):
    """Plots the image masked outside of a circle using masked arrays"""
    # Calculate the distance from the center of the circle
    ny, nx = data.shape
    ix, iy = np.meshgrid(np.arange(nx), np.arange(ny))
    distance = np.sqrt((ix - center_x)**2 + (iy - center_y)**2)

    # Mask portions of the data array outside of the circle
    data = np.ma.masked_where(distance > radius, data)

    # Plot
    plt.figure()
    plt.imshow(data)
    plt.title('Masked Array')

def plot_clipped(data, center_x, center_y, radius):
    """Plots the image clipped outside of a circle by using a clip path"""
    fig = plt.figure()
    ax = fig.add_subplot(111)

    # Make a circle
    circ = patches.Circle((center_x, center_y), radius, facecolor='none')
    ax.add_patch(circ) # Plot the outline

    # Plot the clipped image
    im = ax.imshow(data, clip_path=circ, clip_on=True)

    plt.title('Clipped Array')

main()

enter image description here enter image description here

Edit 2: Plotten eines Maskenpolygons über den ursprünglichen Plot: Hier ist eine etwas detailliertere Beschreibung, wie man ein Polygon, das alles außerhalb des Polygons maskiert, über den aktuellen Plot legt. Anscheinend gibt es keine bessere Möglichkeit, Konturplots zu beschneiden (die ich jedenfalls finden konnte...).

import numpy as np
import matplotlib.pyplot as plt

def main():
    # Contour some regular (fake) data
    grid = np.arange(100).reshape((10,10))
    plt.contourf(grid)

    # Verticies of the clipping polygon in counter-clockwise order
    #  (A triange, in this case)
    poly_verts = [(2, 2), (5, 2.5), (6, 8), (2, 2)]

    mask_outside_polygon(poly_verts)

    plt.show()

def mask_outside_polygon(poly_verts, ax=None):
    """
    Plots a mask on the specified axis ("ax", defaults to plt.gca()) such that
    all areas outside of the polygon specified by "poly_verts" are masked.  

    "poly_verts" must be a list of tuples of the verticies in the polygon in
    counter-clockwise order.

    Returns the matplotlib.patches.PathPatch instance plotted on the figure.
    """
    import matplotlib.patches as mpatches
    import matplotlib.path as mpath

    if ax is None:
        ax = plt.gca()

    # Get current plot limits
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    # Verticies of the plot boundaries in clockwise order
    bound_verts = [(xlim[0], ylim[0]), (xlim[0], ylim[1]), 
                   (xlim[1], ylim[1]), (xlim[1], ylim[0]), 
                   (xlim[0], ylim[0])]

    # A series of codes (1 and 2) to tell matplotlib whether to draw a line or 
    # move the "pen" (So that there's no connecting line)
    bound_codes = [mpath.Path.MOVETO] + (len(bound_verts) - 1) * [mpath.Path.LINETO]
    poly_codes = [mpath.Path.MOVETO] + (len(poly_verts) - 1) * [mpath.Path.LINETO]

    # Plot the masking patch
    path = mpath.Path(bound_verts + poly_verts, bound_codes + poly_codes)
    patch = mpatches.PathPatch(path, facecolor='white', edgecolor='none')
    patch = ax.add_patch(patch)

    # Reset the plot limits to their original extents
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

    return patch

if __name__ == '__main__':
    main()

Clipped contour plot

2voto

gnovice Punkte 124264

Hinweis: Diese Antwort verwendet MATLAB-Syntax, da die Frage ursprünglich als solche gekennzeichnet war. Aber auch wenn Sie matplotlib in Python verwenden, sollte das Konzept dasselbe sein, auch wenn die Syntax etwas anders ist.

Eine Möglichkeit besteht darin, ein Polygon zu erstellen, das erscheint ein Loch zu haben, aber in Wirklichkeit sind es nur zwei Kanten, die sich um einen leeren Raum wickeln und sich berühren. Sie können dies tun, indem Sie einen Satz von x y y Koordinaten, die um den Rand des Kreises verlaufen, dann vom Kreisrand zum Rand eines begrenzenden Quadrats, dann um den Rand dieses Quadrats und zurück zum Kreisrand entlang derselben Linie. Hier ist ein Beispiel mit einem Einheitskreis und einem 4 x 4 Quadrat, das im Ursprung zentriert ist:

theta = linspace(0,2*pi,100);      %# A vector of 100 angles from 0 to 2*pi
xCircle = cos(theta);              %# x coordinates for circle
yCircle = sin(theta);              %# y coordinates for circle
xSquare = [2 2 -2 -2 2 2];         %# x coordinates for square
ySquare = [0 -2 -2 2 2 0];         %# y coordinates for square
hp = fill([xCircle xSquare],...    %# Plot the filled polygon
          [yCircle ySquare],'r');
axis equal                         %# Make axes tick marks equal in size

Und hier ist die Zahl, die Sie sehen sollten:

alt text

Beachten Sie die Linie auf der rechten Seite, die die Kanten des Kreises und des Quadrats verbindet. An dieser Stelle treffen sich zwei Kanten des roten Polygons und berühren sich. Wenn Sie nicht möchten, dass die Kantenlinien sichtbar sind, können Sie ihre Farbe so ändern, dass sie mit der Füllfarbe des Polygons übereinstimmt:

set(hp,'EdgeColor','r');

2voto

Facundo Punkte 81

Da dies das erste Ergebnis ist, das beim Googeln auftaucht matplotlib fill outside Ich werde auf das antworten, was im Titel vorgeschlagen wurde.

Fondo

Von dem, was ich verstehe, Matplotlib bietet keine Funktion, um den Bereich außerhalb eines Polygons zu füllen, aber nur innerhalb es mit Axes.fill . Wenn wir ein größeres "äußeres" Polygon erstellen, das das kleinere enthält, und die beiden so verbinden, dass keine Überschneidungen entstehen, ist es möglich, Matplotlib so zu "täuschen", dass es das innere Polygon für einen Spalt des äußeren Polygons hält. Wenn das äußere Polygon außerhalb der Ansichtsgrenzen gehalten wird, hat dies den Effekt, dass der gesamte Raum außerhalb des inneren Polygons ausgefüllt wird.

polygon setup

Eine Sache, die zu beachten ist, ist die Ausrichtung des Außenpolygons, da der Kanal, der mit dem Außenpolygon verbunden ist, sich nicht selbst schneiden sollte. Daher sollte die Ausrichtung des äußeren Polygons der des inneren Polygons entgegengesetzt sein.

self intersecting path

Lösung

Die folgende Funktion findet die Punkte des inneren Polygons, die der linken unteren Ecke am nächsten liegen, und fügt dort den Pfad für das äußere Polygon ein, wobei die Ausrichtung anhand der vorzeichenbehafteten Fläche des Parallelogramms erfolgt, das durch die Vektoren für den Spleißpunkt und den nächsten Punkt gebildet wird.

import numpy as np

def concat(*arrs) -> np.ndarray:
    return np.concatenate(tuple(map(np.asarray, arrs)))

def insert_at(outer_arr, arr, n) -> np.ndarray:
    outer_arr = np.asarray(outer_arr)
    prev, post = np.split(outer_arr, (n,))
    return concat(prev, arr, post)

def cross2d(x1, y1, x2, y2):
    return x1*y2-x2*y1

def is_clockwise(x1, y1, x2, y2):
    cp = cross2d(x1, y1, x2, y2)
    return cp < 0 if cp != 0 else None

def fill_outside(x, y, ll, ur, counter_clockwise=None):
    """
    Creates a polygon where x and y form a crevice of an outer
    rectangle with lower left and upper right corners `ll` and `ur`
    respectively. If `counter_clockwise` is `None` then the orientation
    of the outer polygon will be guessed to be the opposite of the
    inner connecting points.
    """
    x = np.asarray(x)
    y = np.asarray(y)
    xmin, ymin = ll
    xmax, ymax = ur
    xmin, ymin = min(xmin, min(x)), min(ymin, min(y))
    xmax, ymax = max(xmax, max(x)), max(ymax, max(y))
    corners = np.array([
        [xmin, ymin],
        [xmin, ymax],
        [xmax, ymax],
        [xmax, ymin],
        [xmin, ymin],
    ])
    lower_left = corners[0]
    # Get closest point to splicing corner
    x_off, y_off = x-lower_left[0], y-lower_left[1]
    closest_n = (x_off**2+y_off**2).argmin()
    # Guess orientation
    p = [x_off[closest_n], y_off[closest_n]]
    try:
        pn = [x_off[closest_n+1], y_off[closest_n+1]]
    except IndexError:
        # wrap around if we're at the end of the array
        pn = [x_off[0], y_off[0]]
    if counter_clockwise is None:
        counter_clockwise = not is_clockwise(*p, *pn)
    corners = corners[::-1] if counter_clockwise else corners
    # Join the arrays
    corners = concat(np.array([[x[closest_n], y[closest_n]]]), corners)
    xs, ys = np.transpose(corners)
    return insert_at(x, xs, closest_n), insert_at(y, ys, closest_n)

Exemples

Füllen außerhalb eines einfachen Dreiecks

import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_figwidth(10)

x = [0, 1, 2]
y = [0, 1, 0]
ll, ur = (-.5, -.25), (2.5, 1.25)
x, y = fill_outside(x, y, ll, ur)
ax1.fill(x, y)
ax1.plot(x, y, c="C1")
ax2.fill(x, y)
ax2.set_xlim((ll[0], ur[0]))
ax2.set_ylim((ll[1], ur[1]))

Produziert:

outside of triangle filled

Füllen außerhalb einer beliebigen Form

import numpy as np

def concat(*arrs) -> np.ndarray:
    return np.concatenate(tuple(map(np.asarray, arrs)))

def z_eq_damping(damping, n=100):
    theta = np.arccos(damping)
    u = np.cos(theta)-np.sin(theta)*1j
    x = np.linspace(0, np.pi/u.imag, num=n)
    contour = np.exp(u*x)
    re, im = contour.real, contour.imag
    return concat(re, np.flip(re)), concat(im, np.flip(-im))

fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_figwidth(10)

x, y = z_eq_damping(.7)
ll, ur = (-1, -1), (1, 1)
x, y = fill_outside(x, y, ll, ur)
ax1.fill(x, y)
ax1.plot(x, y, c="C1")
ax2.fill(x, y)
ax2.set_xlim((ll[0], ur[0]))
ax2.set_ylim((ll[1], ur[1]))

Produziert:

filled outside of shape

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X