Совместное использование одной и той же метки для двух графиков с маркерами линий и точек в легенде - PullRequest
0 голосов
/ 14 февраля 2019

У меня есть график с двумя различными сериями кривых, которые я собираюсь изобразить, используя точки и линии.Мне бы хотелось иметь легенду о том, чтобы маркеры линий и точек имели одну и ту же метку.

Я попробовал это предложение, которое хорошо работает, если обе мои серии графиков имеют разные типы точек,вместо линии и точек.Код, который я сейчас использую с неправильной легендой:

import numpy as np
import matplotlib.pyplot as plt

Vs = np.array([0.5, 1, 1.5, 2])
Xs = np.array([[ 0.5, 0.2,  0.7],
  [ 0.5, 0.3,  0.9],
  [ 0.5, 0.5, 0.4],
  [ 0.5, 0.7, 0.4],
  [ 0.5, 0.9, 0.7],
  [ 1, 0.15,  0.9],
  [ 1, 0.35, 0.6],
  [ 1, 0.45, 0.6],
  [ 1, 0.67, 0.5],
  [ 1, 0.85, 0.9],
  [ 1.5, 0.1,  0.9],
  [ 1.5, 0.3, 0.7],
  [ 1.5, 0.76, 0.3],
  [ 1.5, 0.98, 0.4],
  [ 2, 0.21, 0.5],
  [ 2, 0.46, 0.4],
  [ 2, 0.66, 0.3],
  [ 2, 0.76, 0.5],
  [ 2, 0.88, 0.4],
  [ 2, 0.99, 0.4]])


 f, axs = plt.subplots(1, 1, figsize=(2.5,3))
 #-------------------------------------
 axs.set_xlim(0.38,1.0)
 axs.set_ylim(0.0,4.0)
 colors = plt.cm.gist_ncar(np.linspace(0,1,max(Vs)+3))
 for idx,Val in enumerate(Vs):
     axs.plot(Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2],'s',label=r"$Y={}$".format(Val), ms=3, color=colors[idx])
     axs.plot(Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2]*Val/0.3,'-', label=r"$Y={}$".format(Val), ms=3, color=colors[idx])


axs.set_ylim(0.0,4.0)
axs.set_ylabel(r"$Y$    ", labelpad=2)
axs.set_xlabel(r"$X$    ", labelpad=2)
axs.set_yticks([0,0.5,1.0,1.5,2.0, 2.5, 3.0, 3.5, 4.0])
axs.set_xticks([0,0.5,1.0])

axs.legend(fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,handletextpad=0.2, frameon=False)

f.savefig("tmp.pdf")
plt.show()

Есть ли у вас какие-либо предложения по решению этой проблемы?

Ответы [ 2 ]

0 голосов
/ 14 февраля 2019

Применение моего ответа к Как создать два объекта легенды для одного экземпляра сюжета? в этом случае:

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

Vs = np.array([0.5, 1, 1.5, 2])
Xs = np.array([[ 0.5, 0.2,  0.7], [ 0.5, 0.3,  0.9], [ 0.5, 0.5, 0.4],
               [ 0.5, 0.7, 0.4],[ 0.5, 0.9, 0.7], [ 1, 0.15,  0.9],
               [ 1, 0.35, 0.6], [ 1, 0.45, 0.6], [ 1, 0.67, 0.5],
               [ 1, 0.85, 0.9], [ 1.5, 0.1,  0.9], [ 1.5, 0.3, 0.7],
               [ 1.5, 0.76, 0.3], [ 1.5, 0.98, 0.4], [ 2, 0.21, 0.5], 
               [ 2, 0.66, 0.3], [ 2, 0.76, 0.5], [ 2, 0.88, 0.4],
               [ 2, 0.99, 0.4]])


f, axs = plt.subplots(1, 1, figsize=(2.5,3))

axs.set_xlim(0.38,1.0)
axs.set_ylim(0.0,4.0)
colors = plt.cm.gist_ncar(np.linspace(0,1,max(Vs)+3))
for idx,Val in enumerate(Vs):
    axs.plot(Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2],'s',label=r"$Y={}$".format(Val), ms=3, color=colors[idx])
    axs.plot(Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2]*Val/0.3,'-', label=r"$Y={}$".format(Val), ms=3, color=colors[idx])


axs.set_ylim(0.0,4.0)
axs.set_ylabel(r"$Y$    ", labelpad=2)
axs.set_xlabel(r"$X$    ", labelpad=2)
axs.set_yticks([0,0.5,1.0,1.5,2.0, 2.5, 3.0, 3.5, 4.0])
axs.set_xticks([0,0.5,1.0])

h, l = axs.get_legend_handles_labels()
axs.legend(handles=zip(h[::2], h[1::2]), labels=l[::2], 
           handler_map = {tuple: matplotlib.legend_handler.HandlerTuple(None)})


plt.show()

enter image description here

0 голосов
/ 14 февраля 2019

Я бы пошел с созданием пользовательских линий, которые будут показаны в вашей легенде.Вы можете сделать это, сохранив выходные данные каждой команды графика (линейный график возвращает объект matplotlib.lines.Line2D, в котором хранятся стиль линии, стиль маркера, цвет и т. Д.).Затем вы можете зациклить сохраненные строки и создать новые Line2D объекты, которые объединяют свойства двух линий с одинаковым цветом.Сохраняя эти новые Line2D объекты в списке, скажем handles, вы можете затем передать этот список вызову ax.legend():

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

Vs = np.array([0.5, 1, 1.5, 2])
Xs = np.array([[ 0.5, 0.2,  0.7],
  [ 0.5, 0.3,  0.9],
  [ 0.5, 0.5, 0.4],
  [ 0.5, 0.7, 0.4],
  [ 0.5, 0.9, 0.7],
  [ 1, 0.15,  0.9],
  [ 1, 0.35, 0.6],
  [ 1, 0.45, 0.6],
  [ 1, 0.67, 0.5],
  [ 1, 0.85, 0.9],
  [ 1.5, 0.1,  0.9],
  [ 1.5, 0.3, 0.7],
  [ 1.5, 0.76, 0.3],
  [ 1.5, 0.98, 0.4],
  [ 2, 0.21, 0.5],
  [ 2, 0.46, 0.4],
  [ 2, 0.66, 0.3],
  [ 2, 0.76, 0.5],
  [ 2, 0.88, 0.4],
  [ 2, 0.99, 0.4]])


f, axs = plt.subplots(1, 1, figsize=(2.5,3))
#-------------------------------------
axs.set_xlim(0.38,1.0)
axs.set_ylim(0.0,4.0)
colors = plt.cm.gist_ncar(np.linspace(0,1,max(Vs)+3))


##saving the Line2D objects:
lines = []
points = []
for idx,Val in enumerate(Vs):    
    point, = axs.plot(
        Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2],'s',
        label=r"$Y={}$".format(Val), ms=3, color=colors[idx]
    )
    line, = axs.plot(
        Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2]*Val/0.3,'-',
        label=r"$Y={}$".format(Val), ms=3, color=colors[idx]
    )
    points.append(point)
    lines.append(line)

axs.set_ylim(0.0,4.0)
axs.set_ylabel(r"$Y$    ", labelpad=2)
axs.set_xlabel(r"$X$    ", labelpad=2)
axs.set_yticks([0,0.5,1.0,1.5,2.0, 2.5, 3.0, 3.5, 4.0])
axs.set_xticks([0,0.5,1.0])

#axs.legend(fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,handletextpad=0.2, frameon=False)

#f.savefig("tmp.pdf")
##generating the legend handles, with linestyle, markerstyle, color, and label
##copied from the plotted lines:
handles = [
    Line2D(
        [],[], marker=point.get_marker(), linestyle=line.get_linestyle(),
        color = line.get_color(), 
        label = line.get_label(),
    ) for line, point in zip(lines,points)
]

##passing handles as argument to the `legend()` call:
axs.legend(
    handles=handles,
    fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,
    handletextpad=0.2, frameon=False,
    )
plt.show()

Полученное изображение выглядит следующим образом:

result of above code

РЕДАКТИРОВАТЬ :

Следуя примеру, связанному в вопросе, можно создать объект-обработчик, который генерирует искомыйлегенда обрабатывает.Заменив последнюю часть кода выше следующим:

##a dedicated class that holds the lines to be included in the legend entry
class LineContainer:
    def __init__(self, *args):
        args = [line for line in args if isinstance(line,Line2D)]
        if len(args) < 0:
            raise ValueError('At least one line must be passed')
        self._lines = list(args)

    def get_lines(self):
        return self._lines

    def get_label(self):
        ##assuming here that all lines have the same label
        return self._lines[0].get_label()


##adapted from https://stackoverflow.com/a/31530393/2454357
class data_handler(object):

    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
        scale = fontsize / 22
        x0, y0 = handlebox.xdescent, handlebox.ydescent
        width, height = handlebox.width, handlebox.height

        ##use these two lines to control the lengths of the individual line
        ##segments and the spacing between them:

        ##width for individual artists
        l = 0.7*width/len(orig_handle.get_lines()) 
        ##distance between individual artists
        l0 = 0.3*width/len(orig_handle.get_lines()) 

        result = []
        for i, line in enumerate(orig_handle.get_lines()):

            new_line = Line2D([],[])
            new_line.update_from(line)

            ##if no linestyle is defined, plot only the marker:
            if new_line.get_linestyle() in ['None', None]:
                new_line.set_data(
                    [x0+l*(i+0.5)], [y0+height/2]
                )

            ##else plot markers and lines:
            else:
                new_line.set_data(
                    [x0+l*i+l0/2, x0+l*(i+1)-l0/2],
                    [y0+height/2, y0+height/2]
                )
            new_line.set_transform(handlebox.get_transform())
            handlebox.add_artist(new_line)
            result.append(new_line)

        return result


##generating the handles
handles = [
    LineContainer(line, point) for line, point in zip(lines, points)
]

axs.legend(
    handles = handles,
    handler_map={LineContainer: data_handler()},
    fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,
    handletextpad=0.2, frameon=False,
    )
plt.show()

дает следующее изображение:

new legend

...