【问题标题】:How to make radar spider chart with pentagon grid using matplotlib and python?如何使用 matplotlib 和 python 制作带有五边形网格的雷达蜘蛛图?
【发布时间】:2020-12-30 22:35:21
【问题描述】:

我有一张雷达图。需要将网格从圆形更改为五边形。目前,我有这个输出:

而我期待这样的事情:

这是我所拥有的系统的信息:Windows 10(64 位); Python - 3.8.0(32 位); matplotlib - 3.3.3。 我在这里看到的这个问题:How to make a polygon radar (spider) chart in python 但它并没有解决我的问题。 但这对我不起作用,我不明白为什么。我可以简单地复制代码,但结果是 - 外边框变为五边形,但内网格线保持圆形。但它适用于其他人!

程序代码如下:

import numpy as np

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, RegularPolygon
from matplotlib.path import Path
from matplotlib.projections.polar import PolarAxes
from matplotlib.projections import register_projection
from matplotlib.spines import Spine
from matplotlib.transforms import Affine2D


def radar_factory(num_vars, frame='circle'):
    """Create a radar chart with `num_vars` axes.

    This function creates a RadarAxes projection and registers it.

    Parameters
    ----------
    num_vars : int
        Number of variables for radar chart.
    frame : {'circle' | 'polygon'}
        Shape of frame surrounding axes.

    """
    # calculate evenly-spaced axis angles
    theta = np.linspace(0, 2*np.pi, num_vars, endpoint=False)

    class RadarAxes(PolarAxes):

        name = 'radar'

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # rotate plot such that the first axis is at the top
            self.set_theta_zero_location('N')

        def fill(self, *args, closed=True, **kwargs):
            """Override fill so that line is closed by default"""
            return super().fill(closed=closed, *args, **kwargs)

        def plot(self, *args, **kwargs):
            """Override plot so that line is closed by default"""
            lines = super().plot(*args, **kwargs)
            for line in lines:
                self._close_line(line)

        def _close_line(self, line):
            x, y = line.get_data()
            # FIXME: markers at x[0], y[0] get doubled-up
            if x[0] != x[-1]:
                x = np.concatenate((x, [x[0]]))
                y = np.concatenate((y, [y[0]]))
                line.set_data(x, y)

        def set_varlabels(self, labels):
            self.set_thetagrids(np.degrees(theta), labels)

        def _gen_axes_patch(self):
            # The Axes patch must be centered at (0.5, 0.5) and of radius 0.5
            # in axes coordinates.
            if frame == 'circle':
                return Circle((0.5, 0.5), 0.5)
            elif frame == 'polygon':
                return RegularPolygon((0.5, 0.5), num_vars, radius=0.5, edgecolor="k")
            else:
                raise ValueError("unknown value for 'frame': %s" % frame)

        def draw(self, renderer):
            """ Draw. If frame is polygon, make gridlines polygon-shaped """
            if frame == 'polygon':
                gridlines = self.yaxis.get_gridlines()
                for gl in gridlines:
                    gl.get_path()._interpolation_steps = num_vars
            super().draw(renderer)

        def _gen_axes_spines(self):
            if frame == 'circle':
                return super()._gen_axes_spines()
            elif frame == 'polygon':
                # spine_type must be 'left'/'right'/'top'/'bottom'/'circle'.
                spine = Spine(axes=self,
                              spine_type='circle',
                              path=Path.unit_regular_polygon(num_vars))
                # unit_regular_polygon gives a polygon of radius 1 centered at
                # (0, 0) but we want a polygon of radius 0.5 centered at (0.5,
                # 0.5) in axes coordinates.
                spine.set_transform(Affine2D().scale(.5).translate(.5, .5)
                                    + self.transAxes)
                return {'polar': spine}
            else:
                raise ValueError("unknown value for 'frame': %s" % frame)

    register_projection(RadarAxes)
    return theta


data = [['O1', 'O2', 'O3', 'O4', 'O5'],
        ('Title', [
                    [4, 3.5, 4, 2, 3,], 
                    [1.07, 5.95, 2.04, 1.05, 0.00,], 
                  ]
        )]

N = len(data[0])
theta = radar_factory(N, frame='polygon')                                     # polygon  !!!

spoke_labels = data.pop(0)
title, case_data = data[0]
fig, ax = plt.subplots(figsize=(5, 5), subplot_kw=dict(projection='radar'))
fig.subplots_adjust(top=0.85, bottom=0.05)
ax.set_rgrids([0, 1, 2.0, 3.0, 4.0, 5.0, 6])
ax.set_title(title,  position=(0.5, 1.1), ha='center')

for d in case_data:
    line = ax.plot(theta, d)
    ax.fill(theta, d,  alpha=0.25)
ax.set_varlabels(spoke_labels)

plt.show()


【问题讨论】:

  • 我投票决定重新提出这个问题。虽然 ImportanceOfBeingEarnest 的链接脚本可以在 Python3.6/matplotlib3.2.2 上按预期工作,但对于内部网格线,我也可以通过 Python3.8/matplolib3.3.3 获得不同的输出。
  • 那么,问题出在python版本吗?似乎它仅适用于 python 3.7 及更早版本。
  • 是的,降级到 matplotlib 3.2.2 可以恢复以前的行为。升级到 3.3.3 中的某些内容改变了极坐标网格线的生成方式,现在 _interpolation_steps 被标准值覆盖。 Tbf,matplotlib 文档说This attribute is primarily an implementation detail and is not intended for public use.
  • 有没有办法在不降级matplotlib的情况下解决这个问题?
  • 有没有人知道如何在不降级的情况下做到这一点?

标签: python matplotlib radar-chart


【解决方案1】:

我没有足够的声誉来添加评论,所以我会把它记下来作为答案。 prohde 最近将可用作解决方法并适用于 Matplotlib > 3.5 的代码更新添加到问题 19981。 你可以在这里查看: https://github.com/matplotlib/matplotlib/issues/19981

【讨论】:

    【解决方案2】:

    正如@joao-neves 所述,文档将在此PR 中更新。您的示例的工作代码是

    import numpy as np
    
    import matplotlib.pyplot as plt
    from matplotlib.patches import Circle, RegularPolygon
    from matplotlib.path import Path
    from matplotlib.projections.polar import PolarAxes
    from matplotlib.projections import register_projection
    from matplotlib.spines import Spine
    from matplotlib.transforms import Affine2D
    
    
    def radar_factory(num_vars, frame='circle'):
        """Create a radar chart with `num_vars` axes.
    
        This function creates a RadarAxes projection and registers it.
    
        Parameters
        ----------
        num_vars : int
            Number of variables for radar chart.
        frame : {'circle' | 'polygon'}
            Shape of frame surrounding axes.
    
        """
        # calculate evenly-spaced axis angles
        theta = np.linspace(0, 2*np.pi, num_vars, endpoint=False)
        
        class RadarTransform(PolarAxes.PolarTransform):
            def transform_path_non_affine(self, path):
                # Paths with non-unit interpolation steps correspond to gridlines,
                # in which case we force interpolation (to defeat PolarTransform's
                # autoconversion to circular arcs).
                if path._interpolation_steps > 1:
                    path = path.interpolated(num_vars)
                return Path(self.transform(path.vertices), path.codes)
    
        class RadarAxes(PolarAxes):
    
            name = 'radar'
            PolarTransform = RadarTransform
    
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                # rotate plot such that the first axis is at the top
                self.set_theta_zero_location('N')
    
            def fill(self, *args, closed=True, **kwargs):
                """Override fill so that line is closed by default"""
                return super().fill(closed=closed, *args, **kwargs)
    
            def plot(self, *args, **kwargs):
                """Override plot so that line is closed by default"""
                lines = super().plot(*args, **kwargs)
                for line in lines:
                    self._close_line(line)
    
            def _close_line(self, line):
                x, y = line.get_data()
                # FIXME: markers at x[0], y[0] get doubled-up
                if x[0] != x[-1]:
                    x = np.concatenate((x, [x[0]]))
                    y = np.concatenate((y, [y[0]]))
                    line.set_data(x, y)
    
            def set_varlabels(self, labels):
                self.set_thetagrids(np.degrees(theta), labels)
    
            def _gen_axes_patch(self):
                # The Axes patch must be centered at (0.5, 0.5) and of radius 0.5
                # in axes coordinates.
                if frame == 'circle':
                    return Circle((0.5, 0.5), 0.5)
                elif frame == 'polygon':
                    return RegularPolygon((0.5, 0.5), num_vars, radius=0.5, edgecolor="k")
                else:
                    raise ValueError("unknown value for 'frame': %s" % frame)
    
            def draw(self, renderer):
                """ Draw. If frame is polygon, make gridlines polygon-shaped """
                if frame == 'polygon':
                    gridlines = self.yaxis.get_gridlines()
                    for gl in gridlines:
                        gl.get_path()._interpolation_steps = num_vars
                super().draw(renderer)
    
            def _gen_axes_spines(self):
                if frame == 'circle':
                    return super()._gen_axes_spines()
                elif frame == 'polygon':
                    # spine_type must be 'left'/'right'/'top'/'bottom'/'circle'.
                    spine = Spine(axes=self,
                                  spine_type='circle',
                                  path=Path.unit_regular_polygon(num_vars))
                    # unit_regular_polygon gives a polygon of radius 1 centered at
                    # (0, 0) but we want a polygon of radius 0.5 centered at (0.5,
                    # 0.5) in axes coordinates.
                    spine.set_transform(Affine2D().scale(.5).translate(.5, .5)
                                        + self.transAxes)
                    return {'polar': spine}
                else:
                    raise ValueError("unknown value for 'frame': %s" % frame)
    
        register_projection(RadarAxes)
        return theta
    
    
    data = [['O1', 'O2', 'O3', 'O4', 'O5'],
            ('Title', [
                        [4, 3.5, 4, 2, 3,], 
                        [1.07, 5.95, 2.04, 1.05, 0.00,], 
                      ]
            )]
    
    N = len(data[0])
    theta = radar_factory(N, frame='polygon')                                     
    
    spoke_labels = data.pop(0)
    title, case_data = data[0]
    fig, ax = plt.subplots(figsize=(5, 5), subplot_kw=dict(projection='radar'))
    fig.subplots_adjust(top=0.85, bottom=0.05)
    ax.set_rgrids([0, 1, 2.0, 3.0, 4.0, 5.0, 6])
    ax.set_title(title,  position=(0.5, 1.1), ha='center')
    
    for d in case_data:
        line = ax.plot(theta, d)
        ax.fill(theta, d, alpha=0.25, label='_nolegend_')
    ax.set_varlabels(spoke_labels)
    
    plt.show()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多