Fill between arc patches - Matplotlib

雨燕双飞 提交于 2021-02-08 06:37:33

问题


I have an ellipse that I want to solid fill with colors in different sections. To achieve this I'm using arcs patches. I'm currently drawing multiple arcs and using zorder to overlap the appropriate arcs.

Main issues are that I cannot solid fill arc patches and they are not neatly filling the ellipse. It would be great if the areas didn't overlap either so I could use transparency parameters.

import matplotlib.pyplot as plt
import matplotlib as mpl
import math

fig, ax = plt.subplots(figsize = (8,5))
ax.grid(False)

ax.set_xlim(-85,85)
ax.set_ylim(-70,70)

Arc1_xy = -68, 0
Arc2_xy = -48,0
Arc3_xy = 15, 0

Arc4_xy = 68, 0
Arc5_xy = 48,0
Arc6_xy = -15, 0

E_xy = 0,0

angle = math.degrees(math.acos(2/9.15))

Arc1 = mpl.patches.Arc(Arc1_xy, 75, 92, angle = 360, theta2 = angle, theta1 = 360-angle, color = 'w', lw = 1, alpha = 1, hatch = 'oooooo', zorder = 3)
Arc2 = mpl.patches.Arc(Arc2_xy, 64, 94, angle = 180, theta2 = angle, theta1 = 360-angle, color = 'w', lw = 1, alpha = 1, hatch = 'oooooo', zorder = 3)
Arc3 = mpl.patches.Arc(Arc3_xy, 190, 132, angle = -180, theta2 = angle, theta1 = 360-angle, color = 'w', lw = 1, alpha = 1, hatch = 'oooooo', zorder = 1)

Arc4 = mpl.patches.Arc(Arc4_xy, 75, 92, angle = 180, theta2 = angle, theta1 = 360-angle, color = 'w', lw = 1, alpha = 1, hatch = 'oooooo', zorder = 3)
Arc5 = mpl.patches.Arc(Arc5_xy, 64, 94, angle = 0, theta2 = angle, theta1 = 0-angle, color = 'w', lw = 1, alpha = 1, hatch = 'oooooo', zorder = 3)
Arc6 = mpl.patches.Arc(Arc6_xy, 190, 132, angle = 360, theta2 = angle, theta1 = 360-angle, color = 'w', lw = 1, alpha = 1, hatch = 'oooooo', zorder = 1)

Ellipse = mpl.patches.Ellipse(E_xy, 160, 130, lw = 1, color = 'white', alpha = 1, fill = False)

ax.add_patch(Arc1)
ax.add_patch(Arc2)
ax.add_patch(Arc3)
ax.add_patch(Arc4)
ax.add_patch(Arc5)
ax.add_patch(Arc6)

ax.add_patch(Ellipse)

Arc1.set_color('green')
Arc2.set_color('green')
Arc3.set_color('blue')  
Arc4.set_color('red')
Arc5.set_color('red')
Arc6.set_color('purple')   

The main issues are I want to solid fill the different colored sections within the ellipse. Ideally the sections would not overlap so I could use alpha. Also the arcs don't neatly fit within the ellipse.


回答1:


Your question is not very clear. Here is an updated version of your code:

# EDITED, see below

The Arc4 fills the other side of the ellipse. Is it what you expected?

I also added an example on how to use a colormap. There is wide range of colormaps, many of them ranging from 0 to 255 (up to you to normalize to 0.0-1.0).

Another solution could be to draw overlapping ellipses and use the clip_path attribute to only show the required part.


Edit after comment

To fit perfectly the ellipse you have to calculate you arcs to fit it, which is probably difficult (maybe some nice math tricks allow it, but it is out of my knowledge). So my best bet is to draw multiple ellipses of the same size and use clipping paths.

import matplotlib.pyplot as plt
import matplotlib as mpl

def main():
    fig, ax = plt.subplots(figsize = (8,5))
    ax.grid(False)
    ax.set_xlim(-80,80)
    ax.set_ylim(-70,70)

    Arc1_xy = -68, 0
    Arc3_xy = 15, 0
    E_xy = 0,0

    # Use a predefined colormap
    colormap = plt.cm.get_cmap("Set1")

    # Draw multiple ellipses with different colors and hatching style. All are perfectly superposed
    ellipse = mpl.patches.Ellipse( # Base one, with big black line for reference, in background (zorder=0)
        E_xy, 160, 130,
        lw = 2, color = 'k', fill=False, zorder=0)
    area1 = mpl.patches.Ellipse( # A second one, above the base one, using a color from colormap
        E_xy, 160, 130,
        lw=1,  # Just to highlight that we perfectly fit the base ellipse
        color = colormap(0), hatch='o', fill=False, zorder=1)
    area2 = mpl.patches.Ellipse( # Third one, above the others
        E_xy, 160, 130,
        lw=1,  # Just to highlight that we perfectly fit the base ellipse
        color = colormap(1), hatch='..', fill=False, zorder=2)
    # Add more if you want more "sub-areas" in your base ellipse

    # Define some clipping paths
    clip1 = mpl.patches.Ellipse(
        Arc1_xy, 75, 92,
        fill=False,
        ls=":"  # Just to highlight it but you should remove it
        # visible=False  # We do not need to display it, just to use it for clipping
    )
    clip2 = mpl.patches.Ellipse(
        Arc3_xy, 190, 132, angle = -180,
        fill=False,
        ls=":"  # Just to highlight it but you should remove it
        # visible=False  # We do not need to display it, just to use it for clipping
    )

    # Add all your components to your axe
    ax.add_patch(ellipse)
    ax.add_patch(area1)
    ax.add_patch(area2)
    ax.add_patch(clip1)
    ax.add_patch(clip2)

    # Clip the sub-areas with your clipping paths
    area1.set_clip_path(clip2)
    area2.set_clip_path(clip1)

    plt.show()

if __name__ == '__main__':
    main()

This will draw you:

  • A big black elliptic line (the base ellipse)
  • Two hatched (one with dots, one with circles) sub-area inside your base ellipse
  • Thin dotted lines, outlining your clipping paths

You say that you want to "solid fill the ellipse. Not use patch". To solid fill, just play with the fill and color arguments of your areas (and remove hatch if you don't want it). However, I don't see how you can achieve what you want without "patch". Sorry.

Hope this help.


Edit 2

After clarifications here is a new version:

import math

import matplotlib.pyplot as plt
import matplotlib as mpl


def main():
    fig, ax = plt.subplots(figsize=(8, 5))
    ax.grid(False)
    ax.set_xlim(-85, 85)
    ax.set_ylim(-70, 70)

    Arc1_xy = -68, 0
    E_xy = 0, 0

    # Use a predefined colormap
    colormap = plt.cm.get_cmap("Set1")

    # Draw multiple ellipses with different colors and style. All are perfectly superposed
    ellipse = mpl.patches.Ellipse(  # Base one, with big black line for reference
        E_xy, 160, 130,
        lw=2, color='k', fill=False, zorder=100)
    # Ellipses for your sub-areas.
    # Add more if you want more areas
    # Apply the style of your areas here (colors, alpha, hatch, etc.)
    areas = [
        mpl.patches.Ellipse(
            E_xy, 160, 130,  # Perfectly fit your base ellipse
            color=colormap(i), fill=True, alpha=0.5,  # Add some style, fill, color, alpha
            zorder=i)
        for i in range(4)  # Here, we have 4 areas
    ]

    # Define some clipping paths
    # One for each area
    clips = [
        mpl.patches.Arc(  # One covering right half of your ellipse
            E_xy, 160, 130, theta1=-90, theta2=90,
            visible=False  # We do not need to display it, just to use it for clipping
        ),
        mpl.patches.Arc(  # One covering left half of your ellipse
            tuple([-x for x in E_xy]), 160, 130, theta1=90, theta2=-90,
            visible=False  # We do not need to display it, just to use it for clipping
        ),
        mpl.patches.Ellipse(  # A small area on the left
            Arc1_xy, 75, 92,
            visible=False  # We do not need to display it, just to use it for clipping
        ),
        mpl.patches.Ellipse(  # A small area on the right
            tuple([-x for x in Arc1_xy]), 75, 92,
            visible=False  # We do not need to display it, just to use it for clipping
        )
    ]

    # Add all your components to your axe
    ax.add_patch(ellipse)
    for area, clip in zip(areas, clips):
        ax.add_patch(area)
        ax.add_patch(clip)
        area.set_clip_path(clip)  # Use clipping paths to clip you areas

    plt.show()


if __name__ == '__main__':
    main()

And the resulting image:

How does it works is:

  • by drawing multiple ellipses perfectly superposed to the "base" one. This make sure that all your areas are perfectly fitted inside your base ellipse (none of them can outreach)
  • by defining clipping paths that will crop/clip the ellipses to just show part of them. These clipping paths have to cross (or at least follow) the base ellipse borders to make it filled (that's why I change your Arc3, using the ellipse center and dimensions, else there was a white space on the border, inside the ellipse, as you can see on the first figure).

You were not so far with your last try. To style your areas, you have to apply style to your base ellipses (listed in the areas list in my code), not to your clipping paths (listed in the clips list in my code). Both lists, areas and clips need to have the same length! Up to you to add more areas and more clipping paths if you want more sub-areas in your ellipses.

By the way, I see your updated question and your requirements to make the areas not overlapping. This will require more work. You can achieve it by building more complicated clipping path, using the matplotlip.path module.



来源:https://stackoverflow.com/questions/58263608/fill-between-arc-patches-matplotlib

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!