2D-графики не сидят заподлицо со стенами 3D-оси в python mplot3D

Я пытаюсь построить 2D-данные на 3D-оси. У меня есть 3D-форма, работающая с использованием ax.plot_surface, но я не могу заставить 2D-данные располагаться заподлицо со стенками оси, используя ax.plot.

Вот урезанный пример кода, показывающий проблему, с которой я столкнулся с 2D-данными:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Generate Example Data
x = [0.04,0,-0.04]
y = [0.04,0,-0.04]
z = [0.04,0,-0.04]

# Start plotting environment
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Plot 3 lines positioned against the axes "walls"
ax.plot(x,y,-0.08,zdir='z',c='r')
ax.plot(x,z, 0.08,zdir='y',c='g')
ax.plot(y,z,-0.08,zdir='x',c='b')

# Label each axis
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')

# Set each axis limits
ax.set_xlim([-0.08,0.08])
ax.set_ylim([-0.08,0.08]) 
ax.set_zlim([-0.08,0.08]) 

# Equally stretch all axes
ax.set_aspect("equal")

# Set plot size for saving to disk
plt.gcf().set_size_inches(11.7,8.3) 

# Save figure in .eps and .png format
plt.savefig('test.eps', format='eps')
plt.savefig('test.png', format='png', dpi=300)

# Display figure
plt.show()

Это дает следующий результат, из которого вы можете увидеть, что концы строк данных не совпадают с осевыми линиями (т. е. не совпадают с 0,04 и -0,04): Рисунок, показывающий смещение данных относительно стенок осей

Изучая график в интерактивном режиме, я определил, что изменение 0,08 в вызовах ax.plot на величину 0,083 (при сохранении соответствующих знаков) позволяет графикам гораздо более плоско прилегать к стене.

Моя интерпретация этого заключается в том, что график не обеспечивает соблюдение ограничений моей оси, что становится очевидным на графике, если посмотреть на интервалы, где встречаются оси, но с использованием ax.get_xlim() и т. Д. Показывает значения, которые я установил, поэтому я что-то упускаю.

Любые идеи о том, как я могу заставить эти участки сидеть вровень со стенами?

Огромное спасибо,

Тим

Изменить:

Я также попытался установить пределы оси, используя

ax.set_xlim3d(-0.08,0.08)
ax.set_ylim3d(-0.08,0.08) 
ax.set_zlim3d(-0.08,0.08) 

и

 ax.set_xlim3d([-0.08,0.08])
 ax.set_ylim3d([-0.08,0.08]) 
 ax.set_zlim3d([-0.08,0.08])

без везения.

Я определенно склоняюсь к тому, чтобы приписать это проблеме заполнения, когда оси встречаются, но я не могу найти никакой документации по этому поводу. т.е. Я устанавливаю положение графика на -0,08 и предел оси на -0,08, но график добавляет небольшое дополнение в конце, чтобы сделать пределы где-то между -0,082 и -0,083.

Я либо хочу удалить дополнение, либо получить значение заполнения, чтобы я мог ввести его в команду ax.plot.

Изменить2:

Кто-то еще, кто столкнулся с этой проблемой, но не решил проблему Изменение положения стен сетки в фигуре mplot3d


person Timballisto    schedule 12.05.2015    source источник


Ответы (1)


Вы правы, модуль mplot3d содержит функцию, которая добавляет отступы к минимальным и максимальным значениям вашей оси, прежде чем он отобразит ось.

К сожалению, количество отступов жестко запрограммировано и в настоящее время не может быть изменено пользователем в последней доступной версии matplotlib (v2.0).

Решение 1. Измените исходный код

Я обнаружил, что можно отключить дополнительное заполнение, закомментировав две строки исходного кода в файле исходного кода axis3d.py. (В исходном каталоге matplotlib это находится в mpl_toolkits > mplot3d > axis3d.py)

В функции _get_coord_info() функция сначала использует функцию получения get_w_lims() для получения установленных вами пределов x, y и z. Он не изменяет их напрямую, поэтому, например, когда вы проверяете ax.get_xlim(), он по-прежнему возвращает значения 0,08 и -0,08.

def _get_coord_info(self, renderer):
    minx, maxx, miny, maxy, minz, maxz = self.axes.get_w_lims()
    if minx > maxx:
        minx, maxx = maxx, minx
    if miny > maxy:
        miny, maxy = maxy, miny
    if minz > maxz:
        minz, maxz = maxz, minz
    mins = np.array((minx, miny, minz))
    maxs = np.array((maxx, maxy, maxz))
    centers = (maxs + mins) / 2.
    deltas = (maxs - mins) / 12.
    mins = mins - deltas / 4.
    maxs = maxs + deltas / 4.

    vals = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2]
    tc = self.axes.tunit_cube(vals, renderer.M)
    avgz = [tc[p1][2] + tc[p2][2] + tc[p3][2] + tc[p4][2] for \
            p1, p2, p3, p4 in self._PLANES]
    highs = np.array([avgz[2*i] < avgz[2*i+1] for i in range(3)])

    return mins, maxs, centers, deltas, tc, highs

Обратите внимание, что он вычисляет заполнение несколько произвольным образом. Таким образом, отступ не является фиксированным числом, а зависит от установленных вами ограничений оси.

    deltas = (maxs - mins) / 12.
    mins = mins - deltas / 4.
    maxs = maxs + deltas / 4.

Когда функция draw() вызывается для рендеринга оси, она использует эти измененные mins и maxs для построения фактической линии, которую вы видите, поэтому вы всегда получаете отступы на каждом конце оси.

Мое хакерское решение состоит в том, чтобы просто закомментировать две строки следующим образом:

    #mins = mins - deltas / 4.
    #maxs = maxs + deltas / 4.

Даем вам фигуру с линиями, расположенными на одном уровне со стенками 3D-оси.

введите описание изображения здесь

Но обратите внимание, как метки осей в нижнем углу накладываются друг на друга, а метки y кажутся смещенными... Я подозреваю, что именно поэтому жестко закодированные отступы являются функцией. Возможно настроить ось Y отмечайте метки со свойством поворота в методе set_yticklabels(...), пока он не будет соответствовать вашим потребностям.

Решение 2. Используйте масштабирование в интерактивном окне графика

Другое (своеобразное) решение, не требующее изменения исходного кода, состоит в том, чтобы нарисовать фигуру в интерактивном окне, а затем немного увеличить масштаб, пока линии не станут на одном уровне со стеной. Это требует немного проб и ошибок, так как это метод «на глаз». Обратите внимание, что это обычно удаляет метки с самой высокой отметкой, но позволяет избежать проблемы перекрытия в приведенном выше решении.:

введите описание изображения здесь

Решение 3. Объедините все вышеперечисленное

Итак, учитывая, что мы знаем, как mplot3d вычисляет количество отступов, можем ли мы использовать это, чтобы установить пределы осей на нужное количество, чтобы избежать проблем с отступами и без необходимости использовать интерактивный режим или изменять исходный код?

Да, с небольшой дополнительной функцией:

def get_fixed_mins_maxs(mins, maxs):
    deltas = (maxs - mins) / 12.
    mins = mins + deltas / 4.
    maxs = maxs - deltas / 4.

    return [mins, maxs]

minmax = get_fixed_mins_maxs(-0.08, 0.08)

# gives us: [-0.07666666666666667, 0.07666666666666667]

# Set each axis limits with the minmax value from our function
ax.set_xlim(minmax)
ax.set_ylim(minmax) 
ax.set_zlim(minmax) 

Что дает тот же рисунок из Решения 2, без необходимости открывать окно интерактивного графика и оценивать его на глаз.

person decvalts    schedule 21.01.2017