Документация Engee

Преобразования

Страница в процессе перевода.

Каждый график и каждая сцена имеют объект Transformation, который содержит объект transform_func и генерирует матрицу model.

Преобразования моделей

Матрица model состоит из перемещения, масштабирования и поворота, причем поворот действует первым, а перемещение — последним. Перемещение задается с помощью метода translate!(), масштабирование с помощью scale!(), а поворот — с помощью rotate!(). Кроме того, с помощью origin!() можно изменить точку начала, используемую для масштабирования и поворота.

using GLMakie
using GLMakie

box = Rect2f(0.9, -0.1, 0.2, 0.2)

f = Figure(size = (500, 450))
a = Axis(f[1, 1], aspect = DataAspect())
xlims!(a, 0.2, 1.2); a.xticks[] = 0.1:0.2:1.1
ylims!(a, -0.2, 0.8); a.yticks[] = -0.1:0.2:0.7

# Исходный график для справки
scatterlines!(a, box, color = 1:4, markersize = 20, linewidth = 5)

# Преобразованный график
p2 = scatterlines!(a, box, color = 1:4, markersize = 20, linewidth = 5)
origin!(p2, 1,0,0) # применим поворот и масштабирование относительно центра прямоугольника
scale!(p2, 2, 2)             # удвоенные x, y
Makie.rotate!(p2, pi/2)      # поворот на 90°
translate!(p2, -0.5, 0.5)    # преобразуем 0,5 влево, 0,5 вверх

f
6e79cd8

По умолчанию вызов этих функций перезаписывает значение, заданное предыдущим вызовом. Так, при вызове translate!(plot, 1,0,0); translate!(plot, 0,1,0) перемещение будет иметь вид (0,1,0). Для накопления преобразования необходимо добавить Accum в качестве первого аргумента, например translate!(Accum, plot, 0,1,0).

Функция преобразования

transform_func — это функция, которая применяется к входным данным графика после convert_arguments() (нормализация типа) и dim_converts (обработка единиц измерения и категориального значения). Как правило, ей управляет блок Axis. Например, если установить ax.xscale[] = log, для функции преобразования базового ax.scene будет задано (log, identity), и эти значения распространятся на графики внутри оси или сцены.

using Makie
f, a, p = scatter(1:10);
Makie.transform_func(a.scene) # (identity, identity)
Makie.transform_func(p) # (identity, identity)
a.xscale[] = log
Makie.transform_func(a.scene) # (log, identity)
Makie.transform_func(p) # (log, identity)

Можно задать функцию преобразования графика, изменив plot.transformation.transform_func[] = new_func. При этом также изменится функция преобразования каждого дочернего графика. Обратите внимание, что обычно этот параметр сбрасывается при изменении родительской функции преобразования графиков. Например, если в приведенном выше примере задано ax.xscale. Этого можно избежать, явно не наследуя функцию преобразования или создавая объект Transformation самостоятельно. См. сведения ниже.

Преобразование сцены

Сцена также содержит объект Transformation. Он не влияет на сцену напрямую, но выступает в качестве потенциального родительского преобразования для графиков, добавляемых в сцену. Возможность его использования определяется атрибутом transformation. По умолчанию он будет использоваться, если пространство сцены совместимо с данным графиком.

Атрибут transformation

Атрибут transformation управляет инициализацией объекта Transformation. Он обрабатывается один раз во время построения графика, а затем удаляется.

По умолчанию атрибут имеет значение Makie.automatic. В данном случае преобразование наследуется от родительского графика или сцены, если они используют одну и ту же координату space. Таким образом, если родительская сцена использует 3D-камеру, а график — space = :pixel, преобразование не будет унаследовано. Если камера пиксельная, преобразование наследуется. Когда преобразование наследуется, родительская функция transform_func используется повторно, а родительская матрица model выступает в качестве преобразования вторичной матрицы. В псевдокоде мы имеем:

transformation.model = parent.model * local_model
transformation.transform_func = parent.transform_func

Наследование можно контролировать явным образом с помощью пары символов. Во всех этих случаях пространства координат родительского и дочернего объектов игнорируются.

  • :inherit: наследовать и model, и transform_func.

  • :inherit_model: наследовать только model.

  • :inherit_transform_func: наследовать только transform_func.

  • :nothing: не наследовать ни то, ни другое, делая новое преобразование (Transformation) тождественным.

Атрибут transformation также принимает входные данные для функции transform!(). Это позволяет подготовить график с начальным преобразованием модели. Вы можете передать scale, rotation и (или) translation как часть именованного кортежа, словаря или атрибутов, а также повернуть и преобразовать плоскость xy в другую плоскость с помощью (plane, shift). Например:

using CairoMakie
using CairoMakie

f = Figure()
# преобразуем прямоугольник 0..1 в прямоугольник –1..1
lines(f[1, 1], Rect2f(0, 0, 1, 1),
    transformation = (scale = Vec3f(2), translation = Vec3f(-1)))

a = LScene(f[1, 2])
heatmap!(a, rand(4,4), transformation = (:xy, 0.5))
heatmap!(a, rand(4,4), transformation = (:xz, 0.5))
heatmap!(a, rand(4,4), transformation = (:yz, 0.5))
f
f0f0db9

Наконец, вы можете сами сконструировать объект Transformation и передать его через атрибут transformation.

Конструкторы

Как уже упоминалось в разделе о функциях преобразования, существуют случаи, когда может быть полезным самостоятельное построение Transformation. Например, может потребоваться применить transform_func перед запуском алгоритма триангуляции в шаблоне, чтобы триангуляция не исказилась. Или же может быть необходимо объединить в цепочку преобразования ряда графиков, чтобы поддерживать их относительно друг друга (см. ниже). Для выполнения этих задач можно использовать определенные конструкторы.

Чтобы полностью отсоединить график от его родительских преобразований, его можно создать с помощью transformation = Transformation(). Чтобы удалить только transform_func, а не преобразования моделей, можно воспользоваться transformation = Transformation(parent, transform_func = identity). Кроме того, в эти функции можно передавать различные начальные значения для translation, scale и rotation. Это не влияет на то, учитываются ли преобразования родительской модели.

В качестве примера можно привести два рычага на платформе, осуществляющие подъем груза посредством тросового механизма.

using GLMakie
using GLMakie
using Makie: Vec3d

f = Figure(size = (600, 400))
a = Axis(f[2, 2], aspect = DataAspect())
ylims!(0, 3); xlims!(-3, 3)

# Платформа
cart = Transformation()
scatter!(a, [-0.32, -0.15, 0.15, 0.32], fill(0.09, 4), transformation = cart,
    marker = Circle, color = :transparent, strokewidth = 2, strokecolor = :black,
    markerspace = :data, markersize = 0.1
)
linesegments!(a, [-0.4, 0.4], [0.2, 0.2], transformation = cart,
    color = :black, linewidth = 5
)

# рычаги
arm1 = Transformation(cart, origin = Vec3d(0, 0.2, 0))
linesegments!(a, [0, 0], [0.2, 2], transformation = arm1,
    color = :black, linewidth = 5, linecap = :round
)
arm2 = Transformation(arm1, origin = Vec3d(0, 2, 0))
linesegments!(a, [0.0, 1.5], [2, 2], transformation = arm2,
    color = :black, linewidth = 5, linecap = :round
)

# трос — нам требуется вытянуть трос вниз, а не наследовать повороты
rope_length = Observable(1.0)
rope_points = map(arm2.model, rope_length) do model, len
    # позиция конца рычага 2 после применения преобразований
    rope_origin = (model * Point4(1.5, 2, 0, 1))[Vec(1,2)]
    rope_end = rope_origin - Vec2(0, len)
    return [rope_origin, rope_end]
end
crate_origin = map(ps -> ps[2] .+ Vec2(0, -0.12), rope_points)

linesegments!(a, rope_points,
    color = :black, linewidth = 3, linestyle = :dot, linecap = :round
)
scatter!(a, crate_origin,
    marker = Rect, color = :white, strokewidth = 2, strokecolor = :black,
    markerspace = :data, markersize = Vec2f(0.3, 0.2)
)

# Переместить платформу
sl1 = Slider(f[3, 2], range = range(-4, 4, length = 101))
on(v -> translate!(cart, v, 0, 0), sl1.value)
# Повернуть рычаг 1
sl2 = Slider(f[1, 2], range = range(-pi/3, pi/3, length = 101))
on(v -> Makie.rotate!(arm1, -v), sl2.value)
# Повернуть рычаг 2
sl3 = Slider(f[2, 1], range = range(-pi/3, pi/3, length = 101), horizontal = false)
on(v -> Makie.rotate!(arm2, -v), sl3.value)
# Вытянуть трос
sl4 = Slider(f[2, 3], range = range(2, 0.1, length = 101), startvalue = 1.0, horizontal = false)
on(v -> rope_length[] = v, sl4.value)

# Настроить конфигурацию
set_close_to!(sl1, -1.0) # переместить платформу в позицию –1
set_close_to!(sl2, -0.5) # наклонить рычаг 1 влево
set_close_to!(sl3, 0.5) # установить рычаг 2 в горизонтальное положение
set_close_to!(sl4, 0.5) # поднять ящик

f
6728edb