Краткое руководство по Luxor.jl, основному пакету для статической векторной графики.

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

Пакеты векторной графики

Когда дело доходит до векторной графики, вы, вероятно, слышали название Cairo, поскольку это, пожалуй, одна из самых известных библиотек 2D-графики, и она обычно используется пакетами для построения графиков в качестве серверной части. Таким образом, первый вариант рисования вашей векторной графики — это использование Cairo.jl, API Джулии для библиотеки Cairo. Тем не менее, это может быть не очень приятным опытом, поскольку Каир может быть довольно сложным для новичков.

Вот тут-то и появляется Luxor.jl.

Luxor — это пакет Julia для рисования простой статической векторной графики. Он предоставляет базовые функции рисования и утилиты для работы с фигурами, многоугольниками, обтравочными масками, изображениями PNG и SVG, графикой черепах и простой анимацией.

Luxor сосредоточен на простоте и удобстве использования: он должен быть проще в использовании, чем простой Cairo.jl, с более короткими именами, меньшим количеством символов подчеркивания, контекстами по умолчанию и упрощенными функциями. — Документация Luxor.

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

Краткое введение в Luxor.jl

Возможно, первое, что следует отметить в Luxor, это то, что он процедурный и статический, что означает, что вы кодируете серию команд рисования, пока не скажете, что он закончен, что, в свою очередь, создаст PNG, SVG, PDF или EPS на основе того, что вы определили. в начале.

Если вы привыкли к динамическому программированию (вероятно, так оно и есть, поскольку вы используете Джулию), то этот статический способ рисования с помощью Luxor поначалу будет немного странным. Однако есть и обратная сторона.Луксор быстр! Что я имею в виду? Просто попробуйте, и вы увидите, что не только время предварительной компиляции минимально, но и каждая перерисовка также выполняется довольно быстро (особенно по сравнению с пакетами для построения графиков, такими как VegaLite.jl и Plotly.jl).

Давайте сделаем наш первый рисунок в Луксоре.

using Luxor
Drawing(500, 500, "my-drawing.svg")
origin()
setcolor("red")
circle(Point(0, 0), 100, :fill)
finish()

Приведенный выше код очень хорошо инкапсулирует рабочий процесс в Luxor. Самая первая строка — это просто импорт пакета. Во второй строке мы создаем рисунок размером 500 на 500 пикселей, и как только мы закончим рисунок, Luxor создаст файл с именем «my-drawing.svg» в папке, над которой мы работаем. Все хорошо, давайте приступим к третьей строке кода.

Вы можете подумать, что линия orgin() «начинает» рисунок, но что она на самом деле делает, изменяя начало системы координат на центр изображения… Что я имею в виду? Что ж, в Луксоре начало координат по умолчанию находится в левом верхнем углу рисунка, а ось Y на самом деле указывает вниз. Таким образом, функция origin() отвечает за изменение местоположения исходной координаты. И если мы вызовем эту функцию без аргументов, она поместит начало координат в самый центр рисунка (вы можете немного запутаться, если только вы уже не использовали другие пакеты векторной графики, и в этом случае это соглашение может иметь смысл для ты).

Двигаемся дальше, следующая строка — setcolor("red"), и она устанавливает текущий цвет на красный. Здесь важно отметить, что это похоже на выбор текущего цвета ручки для рисования. Следовательно, каждый рисунок, который мы делаем после вызова setcolor("red"), будет красным, пока мы не изменим цвет пера.

Давайте, наконец, нарисуем что-нибудь на нашем холсте. И команда circle(Point(0,0),100,:fill) делает именно это. Эта команда рисует круг с центром в точке (0,0), которая является центром нашей фигуры, так как мы использовали команду origin(). Следующий аргумент задает радиус, который в нашем случае равен 100 пикселям. Наконец, аргумент :fill указывает, что мы должны закрасить внутреннюю часть круга. Опять же, все это очень распространено, если вы раньше работали с векторными рисунками.

Мы завершаем наш рисунок командой finish(), которая затем сохраняет файл чертежа. Теперь, если вы хотите увидеть рисунок сразу после его завершения, вы можете просто использовать функцию preview() , которая покажет самый последний законченный рисунок.

Еще один простой пример

Давайте немного изменим наш первый рисунок.

Drawing(200, 200, "my-drawing.png")
background(0.9, 0.2, 0.2, .5)
setcolor("blue")
setline(10)
setdash("dash")
circle(Point(0, 0), 100, :stroke)
finish()

Это очень похоже на наш первоначальный рисунок, но мы добавили несколько новых функций и удалили origin() . Обратите внимание, что теперь наш круг фактически находится в центре левого верхнего угла нашей фигуры, что теперь можно увидеть благодаря функции background(). Эти числа внутри фоновой функции — это просто спецификация цвета в hsla.

Другое отличие состоит в том, что мы использовали :stroke вместо :fill в функции circle. Таким образом, вместо заполнения круга он просто рисует границу. Функция setline() изменяет толщину обводки, а setdash() изменяет стиль линии. Существует еще много вариантов стилей линий, которые вы можете проверить в строке документации функции.

Луксор для математически склонных

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

Давайте используем Luxor для рисования некоторых диаграмм, которые встречаются в дисциплине под названием Теория категорий 🥸.

using MathTeXEngine
Drawing(200, 200, "my-drawing.png")
# Drawing the borders of our drawing. Note that the `O` is a
# shortcut to Point(0,0).
rect(O,200,200,:stroke)
# Let's change the origin to the center of the drawing
origin()
# We now draw the our diagram
setline(10)
arrow(Point(-40,0),Point(40,0))
# Let's define the font size and write some text
fontsize(15)
text(L"f",Point(0,20), halign = :center)
circle(Point(-50,0),5,:fill)
text(L"ℕ",Point(-50,20),halign = :center)
circle(Point(50,0),5,:fill)
text(L"ℚ",Point(50,20),halign = :center)
#### From here, we are drawing the looping arrows ####
loopx = 30
loopy = 40
adjx = 6
adjy = -2
arrow(
    Point(-50,0) + Point(-adjx,adjy),
    Point(-50,0) + Point(-loopx,-loopy),
    Point(-50,0)+ Point(loopx,-loopy),
    Point(-50,0)+Point(adjx,adjy)
)
arrow(
    Point(50,0) + Point(-adjx,adjy),
    Point(50,0) + Point(-loopx,-loopy),
    Point(50,0) + Point(loopx,-loopy),
    Point(50,0) + Point(adjx,adjy)
)
finish()

Довольно аккуратно, нет? Вы можете быть немного ошеломлены приведенным выше кодом, но просто прочитайте его, и вы увидите, что он довольно прост. Вот некоторые комментарии о вещах, которые могут быть неясными.

Во-первых, вы могли заметить, что мы можем писать LaTeX в Luxor (я знаю, это круто)! Просто напишитеL”\sum^n_{i=10}”, т.е. поставьте «L» перед строкой. MathTeX — это пакет, необходимый для визуализации строки LaTeX без фактической необходимости использования версии LaTeX, установленной на вашем компьютере.

Заключительная часть — это более сложный код, но единственное, что там происходит, — это то, что можно передавать контрольные точки, чтобы рисовать кривые. Следовательно, вместо того, чтобы передавать только начало и конец стрелки, я фактически пропускаю четыре точки, чтобы сделать стрелку петлей, и я также изменяю положение начала и конца, чтобы они не касались круга. loopx и другие определенные переменные — это всего лишь некоторые вспомогательные переменные, которые я использовал для определения контрольных точек, чтобы получить красивую кривую.

Заключительные слова и горячий совет

О Luxor можно сказать еще много чего, так как пакет достаточно обширный. К счастью, как я уже сказал, документы очень хорошие. В нем не только много примеров, но и много объяснений многих функций.

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

Если вы работаете в среде ноутбука, такой как Jupyter, вы можете хранить рисунки в переменных, а не сразу сохранять их в файлы. Кроме того, вы можете сделать небольшие рисунки, а затем поместить их в другой рисунок… Возможно ли это? ДА! Вот как это сделать.

d1 = Drawing(200,200,:svg)
origin()
circle(O+Point(30,0),10,:fill)
finish()
d2 = Drawing(200,200,:svg)
origin()
setcolor("blue")
circle(O,10,:fill)
finish()
d = Drawing(200,200,:svg)
placeimage(d1)
placeimage(d2)
finish()

Я рекомендую всегда работать с svg, если ваш рисунок не слишком тяжелый, так как у вас всегда будет кристально чистое качество. С приведенным выше кодом вы можете просто запустить d в ячейке, чтобы визуализировать рисунок, хранящийся в переменной.

Наконец, вы можете увидеть настоящую спецификацию svg. Вот возможное решение.

dsvg = String(copy(d.bufferdata))

Это даст вам строку svg, которую вы можете просто сохранить в файл mydrawing.svg.

Вот и все. Теперь идите читать документы!